From e553dae3c4fb1a1a2106939d0525bcb80e242e7c Mon Sep 17 00:00:00 2001 From: Stavros Date: Wed, 7 Jan 2026 20:08:37 +0200 Subject: [PATCH] feat: add support for global ip filters --- internal/bootstrap/service_bootstrap.go | 1 + internal/config/config.go | 20 +++++++++++++------- internal/controller/proxy_controller.go | 4 ++-- internal/service/auth_service.go | 13 +++++++++---- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/internal/bootstrap/service_bootstrap.go b/internal/bootstrap/service_bootstrap.go index 6f6a088..130123f 100644 --- a/internal/bootstrap/service_bootstrap.go +++ b/internal/bootstrap/service_bootstrap.go @@ -67,6 +67,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er LoginTimeout: app.config.Auth.LoginTimeout, LoginMaxRetries: app.config.Auth.LoginMaxRetries, SessionCookieName: app.context.sessionCookieName, + IP: app.config.Auth.IP, }, dockerService, ldapService, queries) err = authService.Init() diff --git a/internal/config/config.go b/internal/config/config.go index 2e7af66..61dda81 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,13 +40,19 @@ type ServerConfig struct { } type AuthConfig struct { - Users string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"` - UsersFile string `description:"Path to the users file." yaml:"usersFile"` - SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"` - SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"` - SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"` - LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"` - LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"` + IP IPConfig `description:"IP whitelisting config options." yaml:"ip"` + Users string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"` + UsersFile string `description:"Path to the users file." yaml:"usersFile"` + SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"` + SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"` + SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"` + LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"` + LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"` +} + +type IPConfig struct { + Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"` + Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"` } type OAuthConfig struct { diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index d6c8beb..2520bfb 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -179,9 +179,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { } if userContext.IsLoggedIn { - appAllowed := controller.auth.IsResourceAllowed(c, userContext, acls) + userAllowed := controller.auth.IsUserAllowed(c, userContext, acls) - if !appAllowed { + if !userAllowed { log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource") if req.Proxy == "nginx" || !isBrowser { diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index e71fd5f..bde054a 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -35,6 +35,7 @@ type AuthServiceConfig struct { LoginTimeout int LoginMaxRetries int SessionCookieName string + IP config.IPConfig } type AuthService struct { @@ -352,7 +353,7 @@ func (auth *AuthService) UserAuthConfigured() bool { return len(auth.config.Users) > 0 || auth.ldap != nil } -func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, acls config.App) bool { +func (auth *AuthService) IsUserAllowed(c *gin.Context, context config.UserContext, acls config.App) bool { if context.OAuth { log.Debug().Msg("Checking OAuth whitelist") return utils.CheckFilter(acls.OAuth.Whitelist, context.Email) @@ -435,7 +436,11 @@ func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User { } func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool { - for _, blocked := range acls.Block { + // Merge the global and app IP filter + blockedIps := append(auth.config.IP.Block, acls.Block...) + allowedIPs := append(auth.config.IP.Allow, acls.Allow...) + + 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") @@ -447,7 +452,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool { } } - for _, allowed := range acls.Allow { + 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") @@ -459,7 +464,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool { } } - if len(acls.Allow) > 0 { + if len(allowedIPs) > 0 { log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access") return false }