From 54f893b84fe2a144fae77ffbd49b6b37b44c0119 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 14 Dec 2021 11:59:36 +0100 Subject: [PATCH] flows: add additional sentry spans Signed-off-by: Jens Langhammer --- authentik/flows/stage.py | 19 +++++++++++++++--- authentik/stages/identification/stage.py | 25 ++++++++++++++++-------- authentik/stages/password/stage.py | 15 ++++++++++++-- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/authentik/flows/stage.py b/authentik/flows/stage.py index 4e29637ee..729f9a07a 100644 --- a/authentik/flows/stage.py +++ b/authentik/flows/stage.py @@ -6,6 +6,7 @@ from django.http.response import HttpResponse from django.urls import reverse from django.views.generic.base import View from rest_framework.request import Request +from sentry_sdk.hub import Hub from structlog.stdlib import get_logger from authentik.core.models import DEFAULT_AVATAR, User @@ -94,8 +95,16 @@ class ChallengeStageView(StageView): keep_context=keep_context, ) return self.executor.restart_flow(keep_context) - return self.challenge_invalid(challenge) - return self.challenge_valid(challenge) + with Hub.current.start_span( + op="authentik.flow.stage.challenge_invalid", + description=self.__class__.__name__, + ): + return self.challenge_invalid(challenge) + with Hub.current.start_span( + op="authentik.flow.stage.challenge_valid", + description=self.__class__.__name__, + ): + return self.challenge_valid(challenge) def format_title(self) -> str: """Allow usage of placeholder in flow title.""" @@ -104,7 +113,11 @@ class ChallengeStageView(StageView): } def _get_challenge(self, *args, **kwargs) -> Challenge: - challenge = self.get_challenge(*args, **kwargs) + with Hub.current.start_span( + op="authentik.flow.stage.get_challenge", + description=self.__class__.__name__, + ): + challenge = self.get_challenge(*args, **kwargs) if "flow_info" not in challenge.initial_data: flow_info = ContextualFlowInfo( data={ diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index f8c32674f..8b0368441 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -12,6 +12,7 @@ from django.utils.translation import gettext as _ from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field from rest_framework.fields import BooleanField, CharField, DictField, ListField from rest_framework.serializers import ValidationError +from sentry_sdk.hub import Hub from structlog.stdlib import get_logger from authentik.core.api.utils import PassiveSerializer @@ -90,8 +91,12 @@ class IdentificationChallengeResponse(ChallengeResponse): pre_user = self.stage.get_user(uid_field) if not pre_user: - # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks - sleep(0.30 * SystemRandom().randint(3, 7)) + with Hub.current.start_span( + op="authentik.stages.identification.validate_invalid_wait", + description="Sleep random time on invalid user identifier", + ): + # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks + sleep(0.30 * SystemRandom().randint(3, 7)) LOGGER.debug("invalid_login", identifier=uid_field) identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field) # We set the pending_user even on failure so it's part of the context, even @@ -114,12 +119,16 @@ class IdentificationChallengeResponse(ChallengeResponse): if not password: LOGGER.warning("Password not set for ident+auth attempt") try: - user = authenticate( - self.stage.request, - current_stage.password_stage.backends, - username=self.pre_user.username, - password=password, - ) + with Hub.current.start_span( + op="authentik.stages.identification.authenticate", + description="User authenticate call (combo stage)", + ): + user = authenticate( + self.stage.request, + current_stage.password_stage.backends, + username=self.pre_user.username, + password=password, + ) if not user: raise ValidationError("Failed to authenticate.") self.pre_user = user diff --git a/authentik/stages/password/stage.py b/authentik/stages/password/stage.py index ddcfe84dc..c353102c5 100644 --- a/authentik/stages/password/stage.py +++ b/authentik/stages/password/stage.py @@ -10,6 +10,7 @@ from django.urls import reverse from django.utils.translation import gettext as _ from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import CharField +from sentry_sdk.hub import Hub from structlog.stdlib import get_logger from authentik.core.models import User @@ -43,7 +44,11 @@ def authenticate(request: HttpRequest, backends: list[str], **credentials: Any) LOGGER.warning("Failed to import backend", path=backend_path) continue LOGGER.debug("Attempting authentication...", backend=backend_path) - user = backend.authenticate(request, **credentials) + with Hub.current.start_span( + op="authentik.stages.password.authenticate", + description=backend_path, + ): + user = backend.authenticate(request, **credentials) if user is None: LOGGER.debug("Backend returned nothing, continuing", backend=backend_path) continue @@ -120,7 +125,13 @@ class PasswordStageView(ChallengeStageView): "username": pending_user.username, } try: - user = authenticate(self.request, self.executor.current_stage.backends, **auth_kwargs) + with Hub.current.start_span( + op="authentik.stages.password.authenticate", + description="User authenticate call", + ): + user = authenticate( + self.request, self.executor.current_stage.backends, **auth_kwargs + ) except PermissionDenied: del auth_kwargs["password"] # User was found, but permission was denied (i.e. user is not active)