From 32b0c8bdd7cef2259caf647c06023fd2cb5fdeb5 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Fri, 18 Aug 2023 10:43:06 +0200 Subject: [PATCH] Refactor and cleanup Auth configuration --- cmd/cc-backend/main.go | 5 +--- internal/auth/auth.go | 24 +++++++++---------- internal/auth/jwt.go | 12 ++++------ internal/auth/jwtCookieSession.go | 38 +++++++++++++++---------------- internal/auth/jwtSession.go | 11 ++++----- internal/auth/ldap.go | 38 ++++++++++++++++--------------- internal/auth/local.go | 4 +--- internal/config/config.go | 1 - 8 files changed, 62 insertions(+), 71 deletions(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index dd00046..e165092 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -211,10 +211,7 @@ func main() { var authentication *auth.Authentication if !config.Keys.DisableAuthentication { var err error - if authentication, err = auth.Init(map[string]interface{}{ - "ldap": config.Keys.LdapConfig, - "jwt": config.Keys.JwtConfig, - }); err != nil { + if authentication, err = auth.Init(); err != nil { log.Fatalf("auth initialization failed: %v", err) } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 8c873ad..8c9beef 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -14,6 +14,7 @@ import ( "os" "time" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/repository" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -21,7 +22,6 @@ import ( ) type Authenticator interface { - Init(config interface{}) error CanLogin(user *schema.User, username string, rw http.ResponseWriter, r *http.Request) (*schema.User, bool) Login(user *schema.User, rw http.ResponseWriter, r *http.Request) (*schema.User, error) } @@ -80,7 +80,7 @@ func (auth *Authentication) AuthViaSession( }, nil } -func Init(configs map[string]interface{}) (*Authentication, error) { +func Init() (*Authentication, error) { auth := &Authentication{} sessKey := os.Getenv("SESSION_KEY") @@ -101,9 +101,9 @@ func Init(configs map[string]interface{}) (*Authentication, error) { auth.sessionStore = sessions.NewCookieStore(bytes) } - if config, ok := configs["ldap"]; ok { + if config.Keys.LdapConfig != nil { ldapAuth := &LdapAuthenticator{} - if err := ldapAuth.Init(config); err != nil { + if err := ldapAuth.Init(); err != nil { log.Warn("Error while initializing authentication -> ldapAuth init failed") } else { auth.LdapAuth = ldapAuth @@ -113,32 +113,32 @@ func Init(configs map[string]interface{}) (*Authentication, error) { log.Info("Missing LDAP configuration: No LDAP support!") } - if config, ok := configs["jwt"]; ok { + if config.Keys.JwtConfig != nil { auth.JwtAuth = &JWTAuthenticator{} - if err := auth.JwtAuth.Init(config); err != nil { + if err := auth.JwtAuth.Init(); err != nil { log.Error("Error while initializing authentication -> jwtAuth init failed") return nil, err } jwtSessionAuth := &JWTSessionAuthenticator{} - if err := jwtSessionAuth.Init(config); err != nil { - log.Warn("Error while initializing authentication -> jwtSessionAuth init failed") + if err := jwtSessionAuth.Init(); err != nil { + log.Info("jwtSessionAuth init failed: No JWT login support!") } else { auth.authenticators = append(auth.authenticators, jwtSessionAuth) } jwtCookieSessionAuth := &JWTCookieSessionAuthenticator{} - if err := jwtCookieSessionAuth.Init(configs["jwt"]); err != nil { - log.Warn("Error while initializing authentication -> jwtCookieSessionAuth init failed") + if err := jwtCookieSessionAuth.Init(); err != nil { + log.Info("jwtCookieSessionAuth init failed: No JWT cookie login support!") } else { auth.authenticators = append(auth.authenticators, jwtCookieSessionAuth) } } else { - log.Info("Missing JWT configuration: No JWT token login support!") + log.Info("Missing JWT configuration: No JWT token support!") } auth.LocalAuth = &LocalAuthenticator{} - if err := auth.LocalAuth.Init(nil); err != nil { + if err := auth.LocalAuth.Init(); err != nil { log.Error("Error while initializing authentication -> localAuth init failed") return nil, err } diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 3d87f68..83bfee3 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/repository" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -22,12 +23,9 @@ import ( type JWTAuthenticator struct { publicKey ed25519.PublicKey privateKey ed25519.PrivateKey - config *schema.JWTAuthConfig } -func (ja *JWTAuthenticator) Init(conf interface{}) error { - ja.config = conf.(*schema.JWTAuthConfig) - +func (ja *JWTAuthenticator) Init() error { pubKey, privKey := os.Getenv("JWT_PUBLIC_KEY"), os.Getenv("JWT_PRIVATE_KEY") if pubKey == "" || privKey == "" { log.Warn("environment variables 'JWT_PUBLIC_KEY' or 'JWT_PRIVATE_KEY' not set (token based authentication will not work)") @@ -87,7 +85,7 @@ func (ja *JWTAuthenticator) AuthViaJWT( var roles []string // Validate user + roles from JWT against database? - if ja.config != nil && ja.config.ValidateUser { + if config.Keys.JwtConfig.ValidateUser { ur := repository.GetUserRepository() user, err := ur.GetUser(sub) @@ -130,8 +128,8 @@ func (ja *JWTAuthenticator) ProvideJWT(user *schema.User) (string, error) { "roles": user.Roles, "iat": now.Unix(), } - if ja.config != nil && ja.config.MaxAge != "" { - d, err := time.ParseDuration(ja.config.MaxAge) + if config.Keys.JwtConfig.MaxAge != "" { + d, err := time.ParseDuration(config.Keys.JwtConfig.MaxAge) if err != nil { return "", errors.New("cannot parse max-age config key") } diff --git a/internal/auth/jwtCookieSession.go b/internal/auth/jwtCookieSession.go index 9dcba57..bff08f2 100644 --- a/internal/auth/jwtCookieSession.go +++ b/internal/auth/jwtCookieSession.go @@ -12,6 +12,7 @@ import ( "net/http" "os" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/repository" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -22,15 +23,11 @@ type JWTCookieSessionAuthenticator struct { publicKey ed25519.PublicKey privateKey ed25519.PrivateKey publicKeyCrossLogin ed25519.PublicKey // For accepting externally generated JWTs - - config *schema.JWTAuthConfig } var _ Authenticator = (*JWTCookieSessionAuthenticator)(nil) -func (ja *JWTCookieSessionAuthenticator) Init(conf interface{}) error { - ja.config = conf.(*schema.JWTAuthConfig) - +func (ja *JWTCookieSessionAuthenticator) Init() error { pubKey, privKey := os.Getenv("JWT_PUBLIC_KEY"), os.Getenv("JWT_PRIVATE_KEY") if pubKey == "" || privKey == "" { log.Warn("environment variables 'JWT_PUBLIC_KEY' or 'JWT_PRIVATE_KEY' not set (token based authentication will not work)") @@ -65,17 +62,18 @@ func (ja *JWTCookieSessionAuthenticator) Init(conf interface{}) error { return errors.New("environment variable 'CROSS_LOGIN_JWT_PUBLIC_KEY' not set (cross login token based authentication will not work)") } + jc := config.Keys.JwtConfig // Warn if other necessary settings are not configured - if ja.config != nil { - if ja.config.CookieName == "" { - log.Warn("cookieName for JWTs not configured (cross login via JWT cookie will fail)") + if jc != nil { + if jc.CookieName == "" { + log.Info("cookieName for JWTs not configured (cross login via JWT cookie will fail)") return errors.New("cookieName for JWTs not configured (cross login via JWT cookie will fail)") } - if !ja.config.ValidateUser { - log.Warn("forceJWTValidationViaDatabase not set to true: CC will accept users and roles defined in JWTs regardless of its own database!") + if !jc.ValidateUser { + log.Info("forceJWTValidationViaDatabase not set to true: CC will accept users and roles defined in JWTs regardless of its own database!") } - if ja.config.TrustedIssuer == "" { - log.Warn("trustedExternalIssuer for JWTs not configured (cross login via JWT cookie will fail)") + if jc.TrustedIssuer == "" { + log.Info("trustedExternalIssuer for JWTs not configured (cross login via JWT cookie will fail)") return errors.New("trustedExternalIssuer for JWTs not configured (cross login via JWT cookie will fail)") } } else { @@ -93,9 +91,10 @@ func (ja *JWTCookieSessionAuthenticator) CanLogin( rw http.ResponseWriter, r *http.Request) (*schema.User, bool) { + jc := config.Keys.JwtConfig cookieName := "" - if ja.config != nil && ja.config.CookieName != "" { - cookieName = ja.config.CookieName + if jc.CookieName != "" { + cookieName = jc.CookieName } // Try to read the JWT cookie @@ -115,7 +114,8 @@ func (ja *JWTCookieSessionAuthenticator) Login( rw http.ResponseWriter, r *http.Request) (*schema.User, error) { - jwtCookie, err := r.Cookie(ja.config.CookieName) + jc := config.Keys.JwtConfig + jwtCookie, err := r.Cookie(jc.CookieName) var rawtoken string if err == nil && jwtCookie.Value != "" { @@ -128,7 +128,7 @@ func (ja *JWTCookieSessionAuthenticator) Login( } unvalidatedIssuer, success := t.Claims.(jwt.MapClaims)["iss"].(string) - if success && unvalidatedIssuer == ja.config.TrustedIssuer { + if success && unvalidatedIssuer == jc.TrustedIssuer { // The (unvalidated) issuer seems to be the expected one, // use public cross login key from config return ja.publicKeyCrossLogin, nil @@ -167,7 +167,7 @@ func (ja *JWTCookieSessionAuthenticator) Login( var roles []string - if ja.config.ValidateUser { + if jc.ValidateUser { // Deny any logins for unknown usernames if user == nil { log.Warn("Could not find user from JWT in internal database.") @@ -189,7 +189,7 @@ func (ja *JWTCookieSessionAuthenticator) Login( // (Ask browser to) Delete JWT cookie deletedCookie := &http.Cookie{ - Name: ja.config.CookieName, + Name: jc.CookieName, Value: "", Path: "/", MaxAge: -1, @@ -208,7 +208,7 @@ func (ja *JWTCookieSessionAuthenticator) Login( AuthSource: schema.AuthViaToken, } - if ja.config.SyncUserOnLogin { + if jc.SyncUserOnLogin { if err := repository.GetUserRepository().AddUser(user); err != nil { log.Errorf("Error while adding user '%s' to DB", user.Username) } diff --git a/internal/auth/jwtSession.go b/internal/auth/jwtSession.go index f68de90..2aa194d 100644 --- a/internal/auth/jwtSession.go +++ b/internal/auth/jwtSession.go @@ -12,6 +12,7 @@ import ( "os" "strings" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/repository" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -20,15 +21,11 @@ import ( type JWTSessionAuthenticator struct { loginTokenKey []byte // HS256 key - - config *schema.JWTAuthConfig } var _ Authenticator = (*JWTSessionAuthenticator)(nil) -func (ja *JWTSessionAuthenticator) Init(conf interface{}) error { - ja.config = conf.(*schema.JWTAuthConfig) - +func (ja *JWTSessionAuthenticator) Init() error { if pubKey := os.Getenv("CROSS_LOGIN_JWT_HS512_KEY"); pubKey != "" { bytes, err := base64.StdEncoding.DecodeString(pubKey) if err != nil { @@ -96,7 +93,7 @@ func (ja *JWTSessionAuthenticator) Login( var roles []string - if ja.config.ValidateUser { + if config.Keys.JwtConfig.ValidateUser { // Deny any logins for unknown usernames if user == nil { log.Warn("Could not find user from JWT in internal database.") @@ -142,7 +139,7 @@ func (ja *JWTSessionAuthenticator) Login( AuthSource: schema.AuthViaToken, } - if ja.config.SyncUserOnLogin { + if config.Keys.JwtConfig.SyncUserOnLogin { if err := repository.GetUserRepository().AddUser(user); err != nil { log.Errorf("Error while adding user '%s' to DB", user.Username) } diff --git a/internal/auth/ldap.go b/internal/auth/ldap.go index 529179e..6a8c483 100644 --- a/internal/auth/ldap.go +++ b/internal/auth/ldap.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/repository" "github.com/ClusterCockpit/cc-backend/pkg/log" "github.com/ClusterCockpit/cc-backend/pkg/schema" @@ -19,25 +20,22 @@ import ( ) type LdapAuthenticator struct { - config *schema.LdapConfig syncPassword string } var _ Authenticator = (*LdapAuthenticator)(nil) -func (la *LdapAuthenticator) Init(conf interface{}) error { - - la.config = conf.(*schema.LdapConfig) - +func (la *LdapAuthenticator) Init() error { la.syncPassword = os.Getenv("LDAP_ADMIN_PASSWORD") if la.syncPassword == "" { log.Warn("environment variable 'LDAP_ADMIN_PASSWORD' not set (ldap sync will not work)") } - if la.config != nil && la.config.SyncInterval != "" { - interval, err := time.ParseDuration(la.config.SyncInterval) + if config.Keys.LdapConfig.SyncInterval != "" { + interval, err := time.ParseDuration(config.Keys.LdapConfig.SyncInterval) if err != nil { - log.Warnf("Could not parse duration for sync interval: %v", la.config.SyncInterval) + log.Warnf("Could not parse duration for sync interval: %v", + config.Keys.LdapConfig.SyncInterval) return err } @@ -57,7 +55,7 @@ func (la *LdapAuthenticator) Init(conf interface{}) error { } }() } else { - log.Info("Missing LDAP configuration key sync_interval") + log.Info("LDAP configuration key sync_interval invalid") } return nil @@ -69,12 +67,14 @@ func (la *LdapAuthenticator) CanLogin( rw http.ResponseWriter, r *http.Request) (*schema.User, bool) { + lc := config.Keys.LdapConfig + if user != nil { if user.AuthSource == schema.AuthViaLDAP { return user, true } } else { - if la.config.SyncUserOnLogin { + if lc.SyncUserOnLogin { l, err := la.getLdapConnection(true) if err != nil { log.Error("LDAP connection error") @@ -83,9 +83,9 @@ func (la *LdapAuthenticator) CanLogin( // Search for the given username searchRequest := ldap.NewSearchRequest( - la.config.UserBase, + lc.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&%s(uid=%s))", la.config.UserFilter, username), + fmt.Sprintf("(&%s(uid=%s))", lc.UserFilter, username), []string{"dn", "uid", "gecos"}, nil) sr, err := l.Search(searchRequest) @@ -138,7 +138,7 @@ func (la *LdapAuthenticator) Login( } defer l.Close() - userDn := strings.Replace(la.config.UserBind, "{username}", user.Username, -1) + userDn := strings.Replace(config.Keys.LdapConfig.UserBind, "{username}", user.Username, -1) if err := l.Bind(userDn, r.FormValue("password")); err != nil { log.Errorf("AUTH/LOCAL > Authentication for user %s failed: %v", user.Username, err) @@ -153,6 +153,7 @@ func (la *LdapAuthenticator) Sync() error { const IN_LDAP int = 2 const IN_BOTH int = 3 ur := repository.GetUserRepository() + lc := config.Keys.LdapConfig users := map[string]int{} usernames, err := ur.GetLdapUsernames() @@ -172,9 +173,9 @@ func (la *LdapAuthenticator) Sync() error { defer l.Close() ldapResults, err := l.Search(ldap.NewSearchRequest( - la.config.UserBase, + lc.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - la.config.UserFilter, + lc.UserFilter, []string{"dn", "uid", "gecos"}, nil)) if err != nil { log.Warn("LDAP search error") @@ -198,7 +199,7 @@ func (la *LdapAuthenticator) Sync() error { } for username, where := range users { - if where == IN_DB && la.config.SyncDelOldUsers { + if where == IN_DB && lc.SyncDelOldUsers { ur.DelUser(username) log.Debugf("sync: remove %v (does not show up in LDAP anymore)", username) } else if where == IN_LDAP { @@ -231,14 +232,15 @@ func (la *LdapAuthenticator) Sync() error { // that so that connections can be reused/cached. func (la *LdapAuthenticator) getLdapConnection(admin bool) (*ldap.Conn, error) { - conn, err := ldap.DialURL(la.config.Url) + lc := config.Keys.LdapConfig + conn, err := ldap.DialURL(lc.Url) if err != nil { log.Warn("LDAP URL dial failed") return nil, err } if admin { - if err := conn.Bind(la.config.SearchDN, la.syncPassword); err != nil { + if err := conn.Bind(lc.SearchDN, la.syncPassword); err != nil { conn.Close() log.Warn("LDAP connection bind failed") return nil, err diff --git a/internal/auth/local.go b/internal/auth/local.go index dd6ec2c..002e676 100644 --- a/internal/auth/local.go +++ b/internal/auth/local.go @@ -19,9 +19,7 @@ type LocalAuthenticator struct { var _ Authenticator = (*LocalAuthenticator)(nil) -func (la *LocalAuthenticator) Init( - _ interface{}) error { - +func (la *LocalAuthenticator) Init() error { return nil } diff --git a/internal/config/config.go b/internal/config/config.go index 02a0ba6..08d01c6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,7 +22,6 @@ var Keys schema.ProgramConfig = schema.ProgramConfig{ Archive: json.RawMessage(`{\"kind\":\"file\",\"path\":\"./var/job-archive\"}`), DisableArchive: false, Validate: false, - LdapConfig: nil, SessionMaxAge: "168h", StopJobsExceedingWalltime: 0, ShortRunningJobsDuration: 5 * 60,