feat: configurable component-level logging (#575)

* Refactor logging to use centralized logger utility

- Removed direct usage of zerolog in multiple files and replaced it with a centralized logging utility in the `utils` package.
- Introduced `Loggers` struct to manage different loggers (Audit, HTTP, App) with configurable levels and outputs.
- Updated all relevant files to utilize the new logging structure, ensuring consistent logging practices across the application.
- Enhanced error handling and logging messages for better traceability and debugging.

* refactor: update logging implementation to use new logger structure

* Refactor logging to use tlog package

- Replaced instances of utils logging with tlog in various controllers, services, and middleware.
- Introduced audit logging for login success, login failure, and logout events.
- Created tlog package with structured logging capabilities using zerolog.
- Added tests for the new tlog logger functionality.

* refactor: update logging configuration in environment files

* fix: adding coderabbit suggestions

* fix: ensure correct audit caller

* fix: include reason in audit login failure logs
This commit is contained in:
Pushpinder Singh
2026-01-15 08:57:19 -05:00
committed by GitHub
parent ba2d732415
commit 53bd413046
28 changed files with 486 additions and 214 deletions

View File

@@ -9,10 +9,10 @@ import (
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/gin-gonic/gin"
"github.com/google/go-querystring/query"
"github.com/rs/zerolog/log"
)
var SupportedProxies = []string{"nginx", "traefik", "caddy", "envoy"}
@@ -52,7 +52,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
err := c.BindUri(&req)
if err != nil {
log.Error().Err(err).Msg("Failed to bind URI")
tlog.App.Error().Err(err).Msg("Failed to bind URI")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
@@ -61,7 +61,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
if !slices.Contains(SupportedProxies, req.Proxy) {
log.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
@@ -73,7 +73,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
// Envoy uses the original client method for the external auth request
// so we allow Any standard HTTP method for /api/auth/envoy
if req.Proxy != "envoy" && c.Request.Method != http.MethodGet {
log.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
tlog.App.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
c.Header("Allow", "GET")
c.JSON(405, gin.H{
"status": 405,
@@ -85,9 +85,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
if isBrowser {
log.Debug().Msg("Request identified as (most likely) coming from a browser")
tlog.App.Debug().Msg("Request identified as (most likely) coming from a browser")
} else {
log.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
tlog.App.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
}
uri := c.Request.Header.Get("X-Forwarded-Uri")
@@ -98,12 +98,12 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
acls, err := controller.acls.GetAccessControls(host)
if err != nil {
log.Error().Err(err).Msg("Failed to get access controls for resource")
tlog.App.Error().Err(err).Msg("Failed to get access controls for resource")
controller.handleError(c, req, isBrowser)
return
}
log.Trace().Interface("acls", acls).Msg("ACLs for resource")
tlog.App.Trace().Interface("acls", acls).Msg("ACLs for resource")
clientIP := c.ClientIP()
@@ -119,13 +119,13 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
authEnabled, err := controller.auth.IsAuthEnabled(uri, acls.Path)
if err != nil {
log.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
tlog.App.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
controller.handleError(c, req, isBrowser)
return
}
if !authEnabled {
log.Debug().Msg("Authentication disabled for resource, allowing access")
tlog.App.Debug().Msg("Authentication disabled for resource, allowing access")
controller.setHeaders(c, acls)
c.JSON(200, gin.H{
"status": 200,
@@ -149,7 +149,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
})
if err != nil {
log.Error().Err(err).Msg("Failed to encode unauthorized query")
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
return
}
@@ -163,7 +163,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
context, err := utils.GetContext(c)
if err != nil {
log.Debug().Msg("No user context found in request, treating as not logged in")
tlog.App.Debug().Msg("No user context found in request, treating as not logged in")
userContext = config.UserContext{
IsLoggedIn: false,
}
@@ -171,10 +171,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
userContext = context
}
log.Trace().Interface("context", userContext).Msg("User context from request")
tlog.App.Trace().Interface("context", userContext).Msg("User context from request")
if userContext.Provider == "basic" && userContext.TotpEnabled {
log.Debug().Msg("User has TOTP enabled, denying basic auth access")
tlog.App.Debug().Msg("User has TOTP enabled, denying basic auth access")
userContext.IsLoggedIn = false
}
@@ -182,7 +182,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
userAllowed := controller.auth.IsUserAllowed(c, userContext, acls)
if !userAllowed {
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
if req.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
@@ -197,7 +197,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
})
if err != nil {
log.Error().Err(err).Msg("Failed to encode unauthorized query")
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
return
}
@@ -216,7 +216,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
groupOK := controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
if !groupOK {
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
if req.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
@@ -232,7 +232,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
})
if err != nil {
log.Error().Err(err).Msg("Failed to encode unauthorized query")
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
return
}
@@ -276,7 +276,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
})
if err != nil {
log.Error().Err(err).Msg("Failed to encode redirect URI query")
tlog.App.Error().Err(err).Msg("Failed to encode redirect URI query")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
return
}
@@ -290,14 +290,14 @@ func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
headers := utils.ParseHeaders(acls.Response.Headers)
for key, value := range headers {
log.Debug().Str("header", key).Msg("Setting header")
tlog.App.Debug().Str("header", key).Msg("Setting header")
c.Header(key, value)
}
basicPassword := utils.GetSecret(acls.Response.BasicAuth.Password, acls.Response.BasicAuth.PasswordFile)
if acls.Response.BasicAuth.Username != "" && basicPassword != "" {
log.Debug().Str("username", acls.Response.BasicAuth.Username).Msg("Setting basic auth header")
tlog.App.Debug().Str("username", acls.Response.BasicAuth.Username).Msg("Setting basic auth header")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(acls.Response.BasicAuth.Username, basicPassword)))
}
}