Merge branch 'main' into feat/ldap-groups

This commit is contained in:
Stavros
2026-01-15 16:01:41 +02:00
32 changed files with 522 additions and 246 deletions

View File

@@ -4,8 +4,8 @@ import (
"errors"
"strings"
"github.com/rs/zerolog/log"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
)
type AccessControlsService struct {
@@ -27,12 +27,12 @@ func (acls *AccessControlsService) Init() error {
func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) {
for app, config := range acls.static {
if config.Config.Domain == domain {
log.Debug().Str("name", app).Msg("Found matching container by domain")
tlog.App.Debug().Str("name", app).Msg("Found matching container by domain")
return config, nil
}
if strings.SplitN(domain, ".", 2)[0] == app {
log.Debug().Str("name", app).Msg("Found matching container by app name")
tlog.App.Debug().Str("name", app).Msg("Found matching container by app name")
return config, nil
}
}
@@ -44,11 +44,11 @@ func (acls *AccessControlsService) GetAccessControls(domain string) (config.App,
app, err := acls.lookupStaticACLs(domain)
if err == nil {
log.Debug().Msg("Using ACls from static configuration")
tlog.App.Debug().Msg("Using ACls from static configuration")
return app, nil
}
// Fallback to Docker labels
log.Debug().Msg("Falling back to Docker labels for ACLs")
tlog.App.Debug().Msg("Falling back to Docker labels for ACLs")
return acls.docker.GetLabels(domain)
}

View File

@@ -12,10 +12,10 @@ import (
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/repository"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
)
@@ -73,7 +73,7 @@ func (auth *AuthService) SearchUser(username string) config.UserSearch {
userDN, err := auth.ldap.GetUserDN(username)
if err != nil {
log.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP")
tlog.App.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP")
return config.UserSearch{
Type: "error",
}
@@ -99,24 +99,24 @@ func (auth *AuthService) VerifyUser(search config.UserSearch, password string) b
if auth.ldap != nil {
err := auth.ldap.Bind(search.Username, password)
if err != nil {
log.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
tlog.App.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
return false
}
err = auth.ldap.BindService(true)
if err != nil {
log.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
tlog.App.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
return false
}
return true
}
default:
log.Debug().Str("type", search.Type).Msg("Unknown user type for authentication")
tlog.App.Debug().Str("type", search.Type).Msg("Unknown user type for authentication")
return false
}
log.Warn().Str("username", search.Username).Msg("User authentication failed")
tlog.App.Warn().Str("username", search.Username).Msg("User authentication failed")
return false
}
@@ -127,7 +127,7 @@ func (auth *AuthService) GetLocalUser(username string) config.User {
}
}
log.Warn().Str("username", username).Msg("Local user not found")
tlog.App.Warn().Str("username", username).Msg("Local user not found")
return config.User{}
}
@@ -195,7 +195,7 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
if attempt.FailedAttempts >= auth.config.LoginMaxRetries {
attempt.LockedUntil = time.Now().Add(time.Duration(auth.config.LoginTimeout) * time.Second)
log.Warn().Str("identifier", identifier).Int("timeout", auth.config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
tlog.App.Warn().Str("identifier", identifier).Int("timeout", auth.config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
}
}
@@ -292,7 +292,7 @@ func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error {
}
c.SetCookie(auth.config.SessionCookieName, cookie, int(newExpiry-currentTime), "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true)
log.Trace().Str("username", session.Username).Msg("Session cookie refreshed")
tlog.App.Trace().Str("username", session.Username).Msg("Session cookie refreshed")
return nil
}
@@ -337,7 +337,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
if currentTime-session.CreatedAt > int64(auth.config.SessionMaxLifetime) {
err = auth.queries.DeleteSession(c, cookie)
if err != nil {
log.Error().Err(err).Msg("Failed to delete session exceeding max lifetime")
tlog.App.Error().Err(err).Msg("Failed to delete session exceeding max lifetime")
}
return repository.Session{}, fmt.Errorf("session expired due to max lifetime exceeded")
}
@@ -346,7 +346,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
if currentTime > session.Expiry {
err = auth.queries.DeleteSession(c, cookie)
if err != nil {
log.Error().Err(err).Msg("Failed to delete expired session")
tlog.App.Error().Err(err).Msg("Failed to delete expired session")
}
return repository.Session{}, fmt.Errorf("session expired")
}
@@ -371,18 +371,18 @@ func (auth *AuthService) UserAuthConfigured() bool {
func (auth *AuthService) IsUserAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
if context.OAuth {
log.Debug().Msg("Checking OAuth whitelist")
tlog.App.Debug().Msg("Checking OAuth whitelist")
return utils.CheckFilter(acls.OAuth.Whitelist, context.Email)
}
if acls.Users.Block != "" {
log.Debug().Msg("Checking blocked users")
tlog.App.Debug().Msg("Checking blocked users")
if utils.CheckFilter(acls.Users.Block, context.Username) {
return false
}
}
log.Debug().Msg("Checking users")
tlog.App.Debug().Msg("Checking users")
return utils.CheckFilter(acls.Users.Allow, context.Username)
}
@@ -393,19 +393,19 @@ func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserConte
for id := range config.OverrideProviders {
if context.Provider == id {
log.Info().Str("provider", id).Msg("OAuth groups not supported for this provider")
tlog.App.Info().Str("provider", id).Msg("OAuth groups not supported for this provider")
return true
}
}
for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") {
if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) {
log.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched")
tlog.App.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched")
return true
}
}
log.Debug().Msg("No groups matched")
tlog.App.Debug().Msg("No groups matched")
return false
}
@@ -442,7 +442,7 @@ func (auth *AuthService) IsAuthEnabled(uri string, path config.AppPath) (bool, e
func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User {
username, password, ok := c.Request.BasicAuth()
if !ok {
log.Debug().Msg("No basic auth provided")
tlog.App.Debug().Msg("No basic auth provided")
return nil
}
return &config.User{
@@ -459,11 +459,11 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
for _, blocked := range blockedIps {
res, err := utils.FilterIP(blocked, ip)
if err != nil {
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
tlog.App.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
continue
}
if res {
log.Debug().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
tlog.App.Debug().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
return false
}
}
@@ -471,21 +471,21 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
for _, allowed := range allowedIPs {
res, err := utils.FilterIP(allowed, ip)
if err != nil {
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
tlog.App.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
continue
}
if res {
log.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
tlog.App.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
return true
}
}
if len(allowedIPs) > 0 {
log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
tlog.App.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
return false
}
log.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
tlog.App.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
return true
}
@@ -493,15 +493,15 @@ func (auth *AuthService) IsBypassedIP(acls config.AppIP, ip string) bool {
for _, bypassed := range acls.Bypass {
res, err := utils.FilterIP(bypassed, ip)
if err != nil {
log.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
tlog.App.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
continue
}
if res {
log.Debug().Str("ip", ip).Str("item", bypassed).Msg("IP is in bypass list, allowing access")
tlog.App.Debug().Str("ip", ip).Str("item", bypassed).Msg("IP is in bypass list, allowing access")
return true
}
}
log.Debug().Str("ip", ip).Msg("IP not in bypass list, continuing with authentication")
tlog.App.Debug().Str("ip", ip).Msg("IP not in bypass list, continuing with authentication")
return false
}

View File

@@ -6,10 +6,10 @@ import (
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
container "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
)
type DockerService struct {
@@ -37,7 +37,7 @@ func (docker *DockerService) Init() error {
_, err = docker.client.Ping(docker.context)
if err != nil {
log.Debug().Err(err).Msg("Docker not connected")
tlog.App.Debug().Err(err).Msg("Docker not connected")
docker.isConnected = false
docker.client = nil
docker.context = nil
@@ -45,7 +45,7 @@ func (docker *DockerService) Init() error {
}
docker.isConnected = true
log.Debug().Msg("Docker connected")
tlog.App.Debug().Msg("Docker connected")
return nil
}
@@ -68,7 +68,7 @@ func (docker *DockerService) inspectContainer(containerId string) (container.Ins
func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
if !docker.isConnected {
log.Debug().Msg("Docker not connected, returning empty labels")
tlog.App.Debug().Msg("Docker not connected, returning empty labels")
return config.App{}, nil
}
@@ -90,17 +90,17 @@ func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
for appName, appLabels := range labels.Apps {
if appLabels.Config.Domain == appDomain {
log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain")
tlog.App.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain")
return appLabels, nil
}
if strings.SplitN(appDomain, ".", 2)[0] == appName {
log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name")
tlog.App.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name")
return appLabels, nil
}
}
}
log.Debug().Msg("No matching container found, returning empty labels")
tlog.App.Debug().Msg("No matching container found, returning empty labels")
return config.App{}, nil
}

View File

@@ -12,8 +12,8 @@ import (
"time"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/rs/zerolog/log"
"golang.org/x/oauth2"
)
@@ -117,7 +117,7 @@ func (generic *GenericOAuthService) Userinfo() (config.Claims, error) {
return user, err
}
log.Trace().Str("body", string(body)).Msg("Userinfo response body")
tlog.App.Trace().Str("body", string(body)).Msg("Userinfo response body")
err = json.Unmarshal(body, &user)
if err != nil {

View File

@@ -11,7 +11,7 @@ import (
"github.com/cenkalti/backoff/v5"
ldapgo "github.com/go-ldap/ldap/v3"
"github.com/rs/zerolog/log"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
)
type LdapServiceConfig struct {
@@ -46,7 +46,7 @@ func (ldap *LdapService) Init() error {
return fmt.Errorf("failed to initialize LDAP with mTLS authentication: %w", err)
}
ldap.cert = &cert
log.Info().Msg("Using LDAP with mTLS authentication")
tlog.App.Info().Msg("Using LDAP with mTLS authentication")
// TODO: Add optional extra CA certificates, instead of `InsecureSkipVerify`
/*
@@ -68,12 +68,12 @@ func (ldap *LdapService) Init() error {
for range time.Tick(time.Duration(5) * time.Minute) {
err := ldap.heartbeat()
if err != nil {
log.Error().Err(err).Msg("LDAP connection heartbeat failed")
tlog.App.Error().Err(err).Msg("LDAP connection heartbeat failed")
if reconnectErr := ldap.reconnect(); reconnectErr != nil {
log.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
tlog.App.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
continue
}
log.Info().Msg("Successfully reconnected to LDAP server")
tlog.App.Info().Msg("Successfully reconnected to LDAP server")
}
}
}()
@@ -212,7 +212,7 @@ func (ldap *LdapService) Bind(userDN string, password string) error {
}
func (ldap *LdapService) heartbeat() error {
log.Debug().Msg("Performing LDAP connection heartbeat")
tlog.App.Debug().Msg("Performing LDAP connection heartbeat")
searchRequest := ldapgo.NewSearchRequest(
"",
@@ -234,7 +234,7 @@ func (ldap *LdapService) heartbeat() error {
}
func (ldap *LdapService) reconnect() error {
log.Info().Msg("Reconnecting to LDAP server")
tlog.App.Info().Msg("Reconnecting to LDAP server")
exp := backoff.NewExponentialBackOff()
exp.InitialInterval = 500 * time.Millisecond

View File

@@ -4,8 +4,8 @@ import (
"errors"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/rs/zerolog/log"
"golang.org/x/exp/slices"
)
@@ -49,10 +49,10 @@ func (broker *OAuthBrokerService) Init() error {
for name, service := range broker.services {
err := service.Init()
if err != nil {
log.Error().Err(err).Msgf("Failed to initialize OAuth service: %T", name)
tlog.App.Error().Err(err).Msgf("Failed to initialize OAuth service: %s", name)
return err
}
log.Info().Str("service", name).Msg("Initialized OAuth service")
tlog.App.Info().Str("service", name).Msg("Initialized OAuth service")
}
return nil