mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: ACL labels from environment variables (#422)
* feat: add LabelService to retrieve application labels from environment variables * feat: allow usage of labels from docker and env variables simultaneously Prioritize labels from environment variables over labels from docker labels * fix: handle error returned by label_serive.go/LoadLabels see https://github.com/steveiliop56/tinyauth/pull/422#discussion_r2443443032 * refactor(label_service): use simple loop instead of slices.ContainsFunc to avoid experimental slices package see https://github.com/steveiliop56/tinyauth/pull/422#pullrequestreview-3354632045 * refactor: merge acl logic into one service --------- Co-authored-by: Stavros <steveiliop56@gmail.com>
This commit is contained in:
103
internal/service/access_controls_service.go
Normal file
103
internal/service/access_controls_service.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/utils/decoders"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type AccessControlsService struct {
|
||||
docker *DockerService
|
||||
envACLs config.Apps
|
||||
}
|
||||
|
||||
func NewAccessControlsService(docker *DockerService) *AccessControlsService {
|
||||
return &AccessControlsService{
|
||||
docker: docker,
|
||||
}
|
||||
}
|
||||
|
||||
func (acls *AccessControlsService) Init() error {
|
||||
acls.envACLs = config.Apps{}
|
||||
env := os.Environ()
|
||||
appEnvVars := []string{}
|
||||
|
||||
for _, e := range env {
|
||||
if strings.HasPrefix(e, "TINYAUTH_APPS_") {
|
||||
appEnvVars = append(appEnvVars, e)
|
||||
}
|
||||
}
|
||||
|
||||
err := acls.loadEnvACLs(appEnvVars)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error {
|
||||
if len(appEnvVars) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
envAcls := map[string]string{}
|
||||
|
||||
for _, e := range appEnvVars {
|
||||
parts := strings.SplitN(e, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize key, this should use the same normalization logic as in utils/decoders/decoders.go
|
||||
key := parts[0]
|
||||
key = strings.ToLower(key)
|
||||
key = strings.ReplaceAll(key, "_", ".")
|
||||
value := parts[1]
|
||||
envAcls[key] = value
|
||||
}
|
||||
|
||||
apps, err := decoders.DecodeLabels(envAcls)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
acls.envACLs = apps
|
||||
return nil
|
||||
}
|
||||
|
||||
func (acls *AccessControlsService) lookupEnvACLs(appDomain string) *config.App {
|
||||
if len(acls.envACLs.Apps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for appName, appACLs := range acls.envACLs.Apps {
|
||||
if appACLs.Config.Domain == appDomain {
|
||||
return &appACLs
|
||||
}
|
||||
|
||||
if strings.SplitN(appDomain, ".", 2)[0] == appName {
|
||||
return &appACLs
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) {
|
||||
// First check environment variables
|
||||
envACLs := acls.lookupEnvACLs(appDomain)
|
||||
|
||||
if envACLs != nil {
|
||||
log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables")
|
||||
return *envACLs, nil
|
||||
}
|
||||
|
||||
// Fallback to Docker labels
|
||||
return acls.docker.GetLabels(appDomain)
|
||||
}
|
||||
@@ -289,21 +289,21 @@ func (auth *AuthService) UserAuthConfigured() bool {
|
||||
return len(auth.config.Users) > 0 || auth.ldap != nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, labels config.App) bool {
|
||||
func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
|
||||
if context.OAuth {
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
return utils.CheckFilter(labels.OAuth.Whitelist, context.Email)
|
||||
return utils.CheckFilter(acls.OAuth.Whitelist, context.Email)
|
||||
}
|
||||
|
||||
if labels.Users.Block != "" {
|
||||
if acls.Users.Block != "" {
|
||||
log.Debug().Msg("Checking blocked users")
|
||||
if utils.CheckFilter(labels.Users.Block, context.Username) {
|
||||
if utils.CheckFilter(acls.Users.Block, context.Username) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msg("Checking users")
|
||||
return utils.CheckFilter(labels.Users.Allow, context.Username)
|
||||
return utils.CheckFilter(acls.Users.Allow, context.Username)
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserContext, requiredGroups string) bool {
|
||||
@@ -371,8 +371,8 @@ func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User {
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *AuthService) CheckIP(labels config.AppIP, ip string) bool {
|
||||
for _, blocked := range labels.Block {
|
||||
func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
for _, blocked := range acls.Block {
|
||||
res, err := utils.FilterIP(blocked, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
|
||||
@@ -384,7 +384,7 @@ func (auth *AuthService) CheckIP(labels config.AppIP, ip string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for _, allowed := range labels.Allow {
|
||||
for _, allowed := range acls.Allow {
|
||||
res, err := utils.FilterIP(allowed, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
|
||||
@@ -396,7 +396,7 @@ func (auth *AuthService) CheckIP(labels config.AppIP, ip string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(labels.Allow) > 0 {
|
||||
if len(acls.Allow) > 0 {
|
||||
log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
|
||||
return false
|
||||
}
|
||||
@@ -405,8 +405,8 @@ func (auth *AuthService) CheckIP(labels config.AppIP, ip string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (auth *AuthService) IsBypassedIP(labels config.AppIP, ip string) bool {
|
||||
for _, bypassed := range labels.Bypass {
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user