flows: add additional sentry spans

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-12-14 11:59:36 +01:00
parent b5685ec072
commit 54f893b84f
3 changed files with 46 additions and 13 deletions

View file

@ -6,6 +6,7 @@ from django.http.response import HttpResponse
from django.urls import reverse from django.urls import reverse
from django.views.generic.base import View from django.views.generic.base import View
from rest_framework.request import Request from rest_framework.request import Request
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import DEFAULT_AVATAR, User from authentik.core.models import DEFAULT_AVATAR, User
@ -94,8 +95,16 @@ class ChallengeStageView(StageView):
keep_context=keep_context, keep_context=keep_context,
) )
return self.executor.restart_flow(keep_context) return self.executor.restart_flow(keep_context)
return self.challenge_invalid(challenge) with Hub.current.start_span(
return self.challenge_valid(challenge) 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: def format_title(self) -> str:
"""Allow usage of placeholder in flow title.""" """Allow usage of placeholder in flow title."""
@ -104,7 +113,11 @@ class ChallengeStageView(StageView):
} }
def _get_challenge(self, *args, **kwargs) -> Challenge: 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: if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo( flow_info = ContextualFlowInfo(
data={ data={

View file

@ -12,6 +12,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
from rest_framework.fields import BooleanField, CharField, DictField, ListField from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
@ -90,8 +91,12 @@ class IdentificationChallengeResponse(ChallengeResponse):
pre_user = self.stage.get_user(uid_field) pre_user = self.stage.get_user(uid_field)
if not pre_user: if not pre_user:
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks with Hub.current.start_span(
sleep(0.30 * SystemRandom().randint(3, 7)) 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) LOGGER.debug("invalid_login", identifier=uid_field)
identification_failed.send(sender=self, request=self.stage.request, uid_field=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 # 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: if not password:
LOGGER.warning("Password not set for ident+auth attempt") LOGGER.warning("Password not set for ident+auth attempt")
try: try:
user = authenticate( with Hub.current.start_span(
self.stage.request, op="authentik.stages.identification.authenticate",
current_stage.password_stage.backends, description="User authenticate call (combo stage)",
username=self.pre_user.username, ):
password=password, user = authenticate(
) self.stage.request,
current_stage.password_stage.backends,
username=self.pre_user.username,
password=password,
)
if not user: if not user:
raise ValidationError("Failed to authenticate.") raise ValidationError("Failed to authenticate.")
self.pre_user = user self.pre_user = user

View file

@ -10,6 +10,7 @@ from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import CharField from rest_framework.fields import CharField
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User 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) LOGGER.warning("Failed to import backend", path=backend_path)
continue continue
LOGGER.debug("Attempting authentication...", backend=backend_path) 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: if user is None:
LOGGER.debug("Backend returned nothing, continuing", backend=backend_path) LOGGER.debug("Backend returned nothing, continuing", backend=backend_path)
continue continue
@ -120,7 +125,13 @@ class PasswordStageView(ChallengeStageView):
"username": pending_user.username, "username": pending_user.username,
} }
try: 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: except PermissionDenied:
del auth_kwargs["password"] del auth_kwargs["password"]
# User was found, but permission was denied (i.e. user is not active) # User was found, but permission was denied (i.e. user is not active)