From 03e16b3a14cf75d4b2755c14ab8d2d1e2113a866 Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Mon, 26 Jan 2026 14:31:54 +0100 Subject: [PATCH] root: make logged HTTP headers configurable (#19716) Signed-off-by: Jens Langhammer --- authentik/lib/default.yml | 3 +++ authentik/root/middleware.py | 11 ++++++++++- internal/config/struct.go | 5 +++++ internal/utils/web/middleware.go | 16 ++++++++++++++-- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/authentik/lib/default.yml b/authentik/lib/default.yml index 07e8562ecb..c7d70f548c 100644 --- a/authentik/lib/default.yml +++ b/authentik/lib/default.yml @@ -63,6 +63,9 @@ debug: false debugger: false log_level: info +log: + http_headers: + - User-Agent sessions: unauthenticated_age: days=1 diff --git a/authentik/root/middleware.py b/authentik/root/middleware.py index 3c3446b558..0f07168b0b 100644 --- a/authentik/root/middleware.py +++ b/authentik/root/middleware.py @@ -22,6 +22,7 @@ from sentry_sdk import Scope from structlog.stdlib import get_logger from authentik.core.models import Token, TokenIntents, User, UserTypes +from authentik.lib.config import CONFIG LOGGER = get_logger("authentik.asgi") ACR_AUTHENTIK_SESSION = "goauthentik.io/core/default" @@ -320,6 +321,10 @@ class LoggingMiddleware: def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): self.get_response = get_response + headers = CONFIG.get("log.http_headers", []) + if isinstance(headers, str): + headers = headers.split(",") + self.headers_to_log = headers def __call__(self, request: HttpRequest) -> HttpResponse: start = perf_counter() @@ -334,6 +339,11 @@ class LoggingMiddleware: def log(self, request: HttpRequest, status_code: int, runtime: int, **kwargs): """Log request""" + for header in self.headers_to_log: + header_value = request.headers.get(header) + if not header_value: + continue + kwargs[header.lower().replace("-", "_")] = header_value LOGGER.info( request.get_full_path(), remote=ClientIPMiddleware.get_client_ip(request), @@ -341,6 +351,5 @@ class LoggingMiddleware: scheme=request.scheme, status=status_code, runtime=runtime, - user_agent=request.META.get("HTTP_USER_AGENT", ""), **kwargs, ) diff --git a/internal/config/struct.go b/internal/config/struct.go index a984bf0fa3..1ac4743883 100644 --- a/internal/config/struct.go +++ b/internal/config/struct.go @@ -15,6 +15,7 @@ type Config struct { Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"` Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"` Web WebConfig `yaml:"web" env:", prefix=AUTHENTIK_WEB__"` + Log LogConfig `yaml:"log" env:", prefix=AUTHENTIK_LOG__"` // Outpost specific config // These are only relevant for proxy/ldap outposts, and cannot be set via YAML @@ -105,3 +106,7 @@ type OutpostConfig struct { type WebConfig struct { Path string `yaml:"path" env:"PATH, overwrite"` } + +type LogConfig struct { + HttpHeaders []string `yaml:"http_headers" env:"HTTP_HEADERS, overwrite"` +} diff --git a/internal/utils/web/middleware.go b/internal/utils/web/middleware.go index d061e666db..8f7cd5f623 100644 --- a/internal/utils/web/middleware.go +++ b/internal/utils/web/middleware.go @@ -6,9 +6,11 @@ import ( "fmt" "net" "net/http" + "strings" "time" log "github.com/sirupsen/logrus" + "goauthentik.io/internal/config" ) // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status @@ -71,6 +73,7 @@ type loggingHandler struct { handler http.Handler logger *log.Entry afterHandler afterHandler + headers []string } type afterHandler func(l *log.Entry, r *http.Request) *log.Entry @@ -87,6 +90,7 @@ func NewLoggingHandler(logger *log.Entry, after afterHandler) func(h http.Handle handler: h, logger: logger, afterHandler: after, + headers: config.Get().Log.HttpHeaders, } } } @@ -101,7 +105,7 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.TLS != nil { scheme = "https" } - h.afterHandler(h.logger.WithFields(log.Fields{ + fields := log.Fields{ "remote": req.RemoteAddr, "host": GetHost(req), "runtime": fmt.Sprintf("%0.3f", duration), @@ -110,5 +114,13 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { "size": responseLogger.Size(), "status": responseLogger.Status(), "user_agent": req.UserAgent(), - }), req).Info(url.RequestURI()) + } + for _, h := range h.headers { + hv := req.Header.Get(h) + if hv == "" { + continue + } + fields[strings.ToLower(strings.ReplaceAll(h, "-", "_"))] = hv + } + h.afterHandler(h.logger.WithFields(fields), req).Info(url.RequestURI()) }