diff --git a/authentik/events/utils.py b/authentik/events/utils.py index cf1b1b78b..abd16e470 100644 --- a/authentik/events/utils.py +++ b/authentik/events/utils.py @@ -19,6 +19,7 @@ from django.utils import timezone from django.views.debug import SafeExceptionReporterFilter from geoip2.models import ASN, City from guardian.utils import get_anonymous_user +from rest_framework import serializers from authentik.blueprints.v1.common import YAMLTag from authentik.core.models import User @@ -186,3 +187,17 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]: if new_value is not ...: final_dict[key] = new_value return final_dict + + +class LogSerializer(serializers.Serializer): + timestamp = serializers.DateTimeField() + log_level = serializers.CharField() + message = serializers.CharField() + attributes = serializers.JSONField(default=dict) + + def to_representation(self, instance): + data = super().to_representation(instance) + data["attributes"] = { + k: v for k, v in instance.items() if k not in ["timestamp", "log_level", "message"] + } + return data diff --git a/authentik/outposts/controllers/base.py b/authentik/outposts/controllers/base.py index a3c0cb7d6..354068ac8 100644 --- a/authentik/outposts/controllers/base.py +++ b/authentik/outposts/controllers/base.py @@ -6,6 +6,7 @@ from structlog.stdlib import get_logger from structlog.testing import capture_logs from authentik import __version__, get_build_hash +from authentik.events.utils import LogSerializer from authentik.lib.config import CONFIG from authentik.lib.sentry import SentryIgnoredException from authentik.outposts.models import ( @@ -59,26 +60,17 @@ class BaseController: self.logger = get_logger() self.deployment_ports = [] - # pylint: disable=invalid-name - def up(self): - """Called by scheduled task to reconcile deployment/service/etc""" - raise NotImplementedError - def up_with_logs(self) -> list[str]: """Call .up() but capture all log output and return it.""" with capture_logs() as logs: self.up() - return [x["event"] for x in logs] - - def down(self): - """Handler to delete everything we've created""" - raise NotImplementedError + return [LogSerializer(data=log).data for log in logs] def down_with_logs(self) -> list[str]: """Call .down() but capture all log output and return it.""" with capture_logs() as logs: self.down() - return [x["event"] for x in logs] + return [LogSerializer(data=log).data for log in logs] def __enter__(self): return self diff --git a/authentik/outposts/controllers/kubernetes.py b/authentik/outposts/controllers/kubernetes.py index e3b358078..1da7ef515 100644 --- a/authentik/outposts/controllers/kubernetes.py +++ b/authentik/outposts/controllers/kubernetes.py @@ -12,6 +12,7 @@ from structlog.testing import capture_logs from urllib3.exceptions import HTTPError from yaml import dump_all +from authentik.events.utils import LogSerializer from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler @@ -78,18 +79,6 @@ class KubernetesController(BaseController): PrometheusServiceMonitorReconciler.reconciler_name(), ] - def up(self): - try: - for reconcile_key in self.reconcile_order: - reconciler_cls = self.reconcilers.get(reconcile_key) - if not reconciler_cls: - continue - reconciler = reconciler_cls(self) - reconciler.up() - - except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: - raise ControllerException(str(exc)) from exc - def up_with_logs(self) -> list[str]: try: all_logs = [] @@ -103,24 +92,11 @@ class KubernetesController(BaseController): continue reconciler = reconciler_cls(self) reconciler.up() - all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] + all_logs += [LogSerializer(data=log).data for log in logs] return all_logs except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc - def down(self): - try: - for reconcile_key in self.reconcile_order: - reconciler_cls = self.reconcilers.get(reconcile_key) - if not reconciler_cls: - continue - reconciler = reconciler_cls(self) - self.logger.debug("Tearing down object", name=reconcile_key) - reconciler.down() - - except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: - raise ControllerException(str(exc)) from exc - def down_with_logs(self) -> list[str]: try: all_logs = [] @@ -134,7 +110,7 @@ class KubernetesController(BaseController): continue reconciler = reconciler_cls(self) reconciler.down() - all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] + all_logs += [LogSerializer(data=log).data for log in logs] return all_logs except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: raise ControllerException(str(exc)) from exc diff --git a/authentik/policies/api/policies.py b/authentik/policies/api/policies.py index 11bdab190..0b42af7ea 100644 --- a/authentik/policies/api/policies.py +++ b/authentik/policies/api/policies.py @@ -16,7 +16,7 @@ from authentik.api.decorators import permission_required from authentik.core.api.applications import user_app_cache_key from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer -from authentik.events.utils import sanitize_dict +from authentik.events.utils import LogSerializer from authentik.lib.utils.reflection import all_subclasses from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer from authentik.policies.models import Policy, PolicyBinding @@ -163,11 +163,6 @@ class PolicyViewSet( proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) with capture_logs() as logs: result = proc.execute() - log_messages = [] - for log in logs: - if log.get("process", "") == "PolicyProcess": - continue - log_messages.append(sanitize_dict(log)) - result.log_messages = log_messages + result.log_messages = [LogSerializer(data=log).data for log in logs] response = PolicyTestResultSerializer(result) return Response(response.data)