Skip to content

Commit

Permalink
sources: add Kerberos (#10815)
Browse files Browse the repository at this point in the history
* sources: introduce new property mappings per-user and group

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* sources/ldap: migrate to new property mappings

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint-fix and make gen

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* web changes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix tests

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* update tests

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* remove flatten for generic implem

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* rework migration

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint-fix

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix migrations

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* re-add field migration to property mappings

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix migrations

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* more migrations fixes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* easy fixes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* migrate to propertymappingmanager

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* ruff and small fixes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* move mapping things into a separate class

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* migrations: use using(db_alias)

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* migrations: use built-in variable

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* add docs

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* add release notes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix login reverse

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* refactor source flow manager matching

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* kerberos sync with mode matching

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fixup

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* finish frontend

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Optimised images with calibre/image-actions

* make web

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* add test for internal password update

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix sync tests

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix filter

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* switch to blueprints property mappings, improvements to frontend

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* some more small fixes

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix reverse

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* properly deal with password changes signals

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* actually deal with it properly

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* update docs

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint-fix

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* blueprints: realm as group: make it non default

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* small fixes and improvements

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* wip

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix title

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* add password backend to default flow

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* link docs page properly, add in admin interface, add suggestions for how to apply changes to a fleet of machines

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* add troubleshooting

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix default flow pass backend

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix flaky spnego tests

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* properly convert gssapi name to python str

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix unpickable types

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* make sure the last server token is returned to the client

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/developer-docs/setup/full-dev-environment.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* more docs review

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix missing library

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix missing library again

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix web import

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix sync

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix sync v2

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* fix sync v3

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

---------

Signed-off-by: Marc 'risson' Schmitt <[email protected]>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Tana M Berry <[email protected]>
  • Loading branch information
3 people authored Oct 23, 2024
1 parent d3ebfca commit d817c64
Show file tree
Hide file tree
Showing 60 changed files with 5,849 additions and 827 deletions.
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ runs:
run: |
pipx install poetry || true
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
- name: Setup python and restore poetry
uses: actions/setup-python@v5
with:
Expand Down
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
apt-get update && \
# Required for installing pip packages
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev libkrb5-dev

RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
Expand Down Expand Up @@ -141,7 +141,7 @@ WORKDIR /
# We cannot cache this layer otherwise we'll end up with a bigger image
RUN apt-get update && \
# Required for runtime
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates libkrb5-3 libkadm5clnt-mit12 libkdb5-10 && \
# Required for bootstrap & healtcheck
apt-get install -y --no-install-recommends runit && \
apt-get clean && \
Expand All @@ -161,6 +161,7 @@ COPY ./tests /tests
COPY ./manage.py /
COPY ./blueprints /blueprints
COPY ./lifecycle/ /lifecycle
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
COPY --from=go-builder /go/authentik /bin/authentik
COPY --from=python-deps /ak-root/venv /ak-root/venv
COPY --from=web-builder /work/web/dist/ /web/dist/
Expand Down
6 changes: 4 additions & 2 deletions authentik/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,13 @@ def is_staff(self) -> bool:
"""superuser == staff user"""
return self.is_superuser # type: ignore

def set_password(self, raw_password, signal=True):
def set_password(self, raw_password, signal=True, sender=None):
if self.pk and signal:
from authentik.core.signals import password_changed

password_changed.send(sender=self, user=self, password=raw_password)
if not sender:
sender = self
password_changed.send(sender=sender, user=self, password=raw_password)
self.password_change_date = now()
return super().set_password(raw_password)

Expand Down
4 changes: 4 additions & 0 deletions authentik/lib/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ ldap:
tls:
ciphers: null

sources:
kerberos:
task_timeout_hours: 2

reputation:
expiry: 86400

Expand Down
1 change: 1 addition & 0 deletions authentik/root/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"authentik.providers.scim",
"authentik.rbac",
"authentik.recovery",
"authentik.sources.kerberos",
"authentik.sources.ldap",
"authentik.sources.oauth",
"authentik.sources.plex",
Expand Down
Empty file.
Empty file.
31 changes: 31 additions & 0 deletions authentik/sources/kerberos/api/property_mappings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Kerberos Property Mapping API"""

from rest_framework.viewsets import ModelViewSet

from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.kerberos.models import KerberosSourcePropertyMapping


class KerberosSourcePropertyMappingSerializer(PropertyMappingSerializer):
"""Kerberos PropertyMapping Serializer"""

class Meta(PropertyMappingSerializer.Meta):
model = KerberosSourcePropertyMapping


class KerberosSourcePropertyMappingFilter(PropertyMappingFilterSet):
"""Filter for KerberosSourcePropertyMapping"""

class Meta(PropertyMappingFilterSet.Meta):
model = KerberosSourcePropertyMapping


class KerberosSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""KerberosSource PropertyMapping Viewset"""

queryset = KerberosSourcePropertyMapping.objects.all()
serializer_class = KerberosSourcePropertyMappingSerializer
filterset_class = KerberosSourcePropertyMappingFilter
search_fields = ["name"]
ordering = ["name"]
114 changes: 114 additions & 0 deletions authentik/sources/kerberos/api/source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Source API Views"""

from django.core.cache import cache
from drf_spectacular.utils import extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.events.api.tasks import SystemTaskSerializer
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.tasks import CACHE_KEY_STATUS


class KerberosSourceSerializer(SourceSerializer):
"""Kerberos Source Serializer"""

connectivity = SerializerMethodField()

def get_connectivity(self, source: KerberosSource) -> dict[str, str] | None:
"""Get cached source connectivity"""
return cache.get(CACHE_KEY_STATUS + source.slug, None)

class Meta:
model = KerberosSource
fields = SourceSerializer.Meta.fields + [
"group_matching_mode",
"realm",
"krb5_conf",
"sync_users",
"sync_users_password",
"sync_principal",
"sync_password",
"sync_keytab",
"sync_ccache",
"connectivity",
"spnego_server_name",
"spnego_keytab",
"spnego_ccache",
"password_login_update_internal_password",
]
extra_kwargs = {
"sync_password": {"write_only": True},
"sync_keytab": {"write_only": True},
"spnego_keytab": {"write_only": True},
}


class KerberosSyncStatusSerializer(PassiveSerializer):
"""Kerberos Source sync status"""

is_running = BooleanField(read_only=True)
tasks = SystemTaskSerializer(many=True, read_only=True)


class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
"""Kerberos Source Viewset"""

queryset = KerberosSource.objects.all()
serializer_class = KerberosSourceSerializer
lookup_field = "slug"
filterset_fields = [
"name",
"slug",
"enabled",
"realm",
"sync_users",
"sync_users_password",
"sync_principal",
"spnego_server_name",
"password_login_update_internal_password",
]
search_fields = [
"name",
"slug",
"realm",
"krb5_conf",
"sync_principal",
"spnego_server_name",
]
ordering = ["name"]

@extend_schema(
responses={
200: KerberosSyncStatusSerializer(),
}
)
@action(
methods=["GET"],
detail=True,
pagination_class=None,
url_path="sync/status",
filter_backends=[],
)
def sync_status(self, request: Request, slug: str) -> Response:
"""Get source's sync status"""
source: KerberosSource = self.get_object()
tasks = list(
get_objects_for_user(request.user, "authentik_events.view_systemtask").filter(
name="kerberos_sync",
uid__startswith=source.slug,
)
)
with source.sync_lock as lock_acquired:
status = {
"tasks": tasks,
"is_running": not lock_acquired,
}
return Response(KerberosSyncStatusSerializer(status).data)
51 changes: 51 additions & 0 deletions authentik/sources/kerberos/api/source_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Kerberos Source Serializer"""

from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import ModelViewSet

from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.core.api.sources import (
GroupSourceConnectionSerializer,
GroupSourceConnectionViewSet,
UserSourceConnectionSerializer,
)
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.kerberos.models import (
GroupKerberosSourceConnection,
UserKerberosSourceConnection,
)


class UserKerberosSourceConnectionSerializer(UserSourceConnectionSerializer):
"""Kerberos Source Serializer"""

class Meta:
model = UserKerberosSourceConnection
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]


class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset"""

queryset = UserKerberosSourceConnection.objects.all()
serializer_class = UserKerberosSourceConnectionSerializer
filterset_fields = ["source__slug"]
search_fields = ["source__slug"]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["source__slug"]


class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
"""OAuth Group-Source connection Serializer"""

class Meta(GroupSourceConnectionSerializer.Meta):
model = GroupKerberosSourceConnection


class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet):
"""Group-source connection Viewset"""

queryset = GroupKerberosSourceConnection.objects.all()
serializer_class = GroupKerberosSourceConnectionSerializer
13 changes: 13 additions & 0 deletions authentik/sources/kerberos/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""authentik kerberos source config"""

from authentik.blueprints.apps import ManagedAppConfig


class AuthentikSourceKerberosConfig(ManagedAppConfig):
"""Authentik source kerberos app config"""

name = "authentik.sources.kerberos"
label = "authentik_sources_kerberos"
verbose_name = "authentik Sources.Kerberos"
mountpoint = "source/kerberos/"
default = True
116 changes: 116 additions & 0 deletions authentik/sources/kerberos/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""authentik Kerberos Authentication Backend"""

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

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 (
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)

LOGGER = get_logger()


class KerberosBackend(InbuiltBackend):
"""Authenticate users against Kerberos realm"""

def authenticate(self, request: HttpRequest, **kwargs):
"""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)

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

def auth_user(
self, username: str, realm: str | None, password: str, **filters
) -> tuple[User | None, KerberosSource | None]:
sources = KerberosSource.objects.filter(enabled=True)
user = User.objects.filter(usersourceconnection__source__in=sources, **filters).first()

if user is not None:
# User found, let's get its connections for the sources that are available
user_source_connections = UserKerberosSourceConnection.objects.filter(
user=user, source__in=sources
)
elif realm is not None:
user_source_connections = UserKerberosSourceConnection.objects.filter(
source__in=sources, identifier=f"{username}@{realm}"
)
# no realm specified, we can't do anything
else:
user_source_connections = UserKerberosSourceConnection.objects.none()

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

for user_source_connection in user_source_connections.prefetch_related().select_related(
"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):
# Password was successful in kinit to Kerberos, so we save it in database
if (
user_source_connection.source.kerberossource.password_login_update_internal_password
):
LOGGER.debug(
"Updating user's password in DB",
source=user_source_connection.source,
user=user_source_connection.user,
)
user_source_connection.user.set_password(
password, sender=user_source_connection.source
)
user_source_connection.user.save()
return user, user_source_connection.source
# Password doesn't match, onto next source
LOGGER.debug(
"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

def auth_user_by_kinit(
self, user_source_connection: UserKerberosSourceConnection, password: str
) -> bool:
"""Attempt authentication by kinit to the source."""
LOGGER.debug(
"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(
user_source_connection.identifier.encode(), gssapi.raw.NameType.kerberos_principal
)
try:
# 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())
# 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
Loading

0 comments on commit d817c64

Please sign in to comment.