From 011ee866dfd98b18fcefb969828e36ec02edeb5f Mon Sep 17 00:00:00 2001 From: jamesread Date: Fri, 13 Feb 2026 23:53:43 +0000 Subject: [PATCH] feat: template parsing for env in password fields --- service/internal/config/config.go | 3 +-- service/internal/config/sanitize.go | 31 +++++++++++++++++------------ service/internal/env/env.go | 17 ++++++++++++++++ service/internal/tpl/templates.go | 16 ++------------- 4 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 service/internal/env/env.go diff --git a/service/internal/config/config.go b/service/internal/config/config.go index 79a92d5..de5bdcc 100644 --- a/service/internal/config/config.go +++ b/service/internal/config/config.go @@ -171,8 +171,7 @@ type Config struct { BannerCSS string `koanf:"bannerCss"` Include string `koanf:"include"` - sourceFiles []string - passwordTemplateParser func(string, interface{}) string + sourceFiles []string } type AuthLocalUsersConfig struct { diff --git a/service/internal/config/sanitize.go b/service/internal/config/sanitize.go index 779cfe1..43364f5 100644 --- a/service/internal/config/sanitize.go +++ b/service/internal/config/sanitize.go @@ -2,7 +2,9 @@ package config import ( "strings" + "text/template" + "github.com/OliveTin/OliveTin/internal/env" "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -173,26 +175,29 @@ func (cfg *Config) sanitizeLogHistoryPageSize() { } } -// SetPasswordTemplateParser sets the function to use for parsing password templates. -// This is called from main.go to avoid import cycles (config can't import entities). -func (cfg *Config) SetPasswordTemplateParser(parser func(string, interface{}) string) { - cfg.passwordTemplateParser = parser -} - func (cfg *Config) sanitizeLocalUserPasswords() { - if cfg.passwordTemplateParser == nil { - return - } - for _, user := range cfg.AuthLocalUsers.Users { if user.Password != "" { - // Parse password as template to support environment variables and other template values - // Note: .CurrentEntity is nil in this context as local users are not entity-bound - user.Password = cfg.passwordTemplateParser(user.Password, nil) + user.Password = parsePasswordTemplate(user.Password) } } } +// parsePasswordTemplate expands {{ .Env.VAR }} in local user password fields using the process environment. +func parsePasswordTemplate(source string) string { + t, err := template.New("password").Option("missingkey=error").Parse(source) + if err != nil { + log.WithFields(log.Fields{"error": err}).Debug("Password template parse failed, using literal") + return source + } + var b strings.Builder + if err := t.Execute(&b, map[string]interface{}{"Env": env.BuildEnvMap()}); err != nil { + log.WithFields(log.Fields{"error": err}).Debug("Password template execute failed, using literal") + return source + } + return b.String() +} + func getActionID(action *Action) string { if action.ID == "" { return uuid.NewString() diff --git a/service/internal/env/env.go b/service/internal/env/env.go new file mode 100644 index 0000000..995da66 --- /dev/null +++ b/service/internal/env/env.go @@ -0,0 +1,17 @@ +package env + +import ( + "os" + "strings" +) + +func BuildEnvMap() map[string]string { + envMap := make(map[string]string) + for _, e := range os.Environ() { + parts := strings.SplitN(e, "=", 2) + if len(parts) == 2 { + envMap[parts[0]] = parts[1] + } + } + return envMap +} diff --git a/service/internal/tpl/templates.go b/service/internal/tpl/templates.go index bf0a242..580b907 100644 --- a/service/internal/tpl/templates.go +++ b/service/internal/tpl/templates.go @@ -2,12 +2,12 @@ package tpl import ( "fmt" - "os" "regexp" "strings" "text/template" "github.com/OliveTin/OliveTin/internal/entities" + "github.com/OliveTin/OliveTin/internal/env" "github.com/OliveTin/OliveTin/internal/installationinfo" log "github.com/sirupsen/logrus" ) @@ -49,7 +49,7 @@ func init() { Runtime: installationinfo.Runtime, } - cachedEnvMap = buildEnvMap() + cachedEnvMap = env.BuildEnvMap() } func GetNewGeneralTemplateContext() *generalTemplateContext { @@ -59,18 +59,6 @@ func GetNewGeneralTemplateContext() *generalTemplateContext { } } -func buildEnvMap() map[string]string { - envMap := make(map[string]string) - for _, env := range os.Environ() { - parts := strings.SplitN(env, "=", 2) - if len(parts) == 2 { - envMap[parts[0]] = parts[1] - } - } - - return envMap -} - func migrateLegacyEntityProperties(rawShellCommand string) string { foundArgumentNames := legacyEntityPropertiesRegex.FindAllStringSubmatch(rawShellCommand, -1)