From 8b9c8c14d5c97aae952179af641091ef070bafcd Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Mon, 31 Aug 2026 18:48:48 +0000 Subject: [PATCH] fix issue with user realm sync and insufficient permissions using priviledge escalation --- app/common/types.go | 5 +++ app/main.go | 86 ++++++++------------------------------------- app/operations.go | 20 +++++------ app/pve/pve.go | 72 +++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 82 deletions(-) diff --git a/app/common/types.go b/app/common/types.go index e5fd8c6..9b87cd0 100644 --- a/app/common/types.go +++ b/app/common/types.go @@ -2,6 +2,11 @@ package app import paas "proxmoxaas-common-lib" +type Realm struct { + Type string + Config any +} + type Backend interface { NewPool(poolname string, pool Pool) (int, error) ModPool(poolname string, pool Pool) (int, error) diff --git a/app/main.go b/app/main.go index 6d58cb8..9bb6749 100644 --- a/app/main.go +++ b/app/main.go @@ -1,9 +1,7 @@ package app import ( - "context" "crypto/rand" - "crypto/tls" "flag" "fmt" "log" @@ -11,18 +9,13 @@ import ( "strconv" common "access-manager-api/app/common" + "access-manager-api/app/pve" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" - "github.com/luthermonson/go-proxmox" ) -type Realm struct { - Type string - Config any -} - type UserSession struct { PVE common.Backend Realm struct { @@ -34,8 +27,9 @@ type UserSession struct { var Version = "1.0.0" var Config common.Config +var RootSession *UserSession var UserSessions map[string]*UserSession -var Realms map[string]Realm +var Realms map[string]common.Realm func Run() { configPath := flag.String("config", "config.json", "path to config.json file") @@ -53,9 +47,18 @@ func Run() { // setup api auth cookies SetupAPISessionStore(router, &Config) + // setup root api token + client, code, err := pve.NewClientFromAPIToken(Config.PVE) + if err != nil { + log.Fatalf("error initializing pve root client: %d %s", code, err) + } + RootSession = &UserSession{ + PVE: client, + } + // get realms from proxmox - Realms = make(map[string]Realm) - Realms = GetRealmsFromPVE(&Config) + Realms = make(map[string]common.Realm) + Realms = RootSession.PVE.(pve.ProxmoxClient).GetRealms() // make global session map UserSessions = make(map[string]*UserSession) @@ -82,7 +85,7 @@ func Run() { log.Printf("Starting Access Manager API on port %s\n", strconv.Itoa(Config.ListenPort)) - err := router.Run("0.0.0.0:" + strconv.Itoa(Config.ListenPort)) + err = router.Run("0.0.0.0:" + strconv.Itoa(Config.ListenPort)) if err != nil { log.Fatalf("Error starting router: %s", err.Error()) } @@ -118,62 +121,3 @@ func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) { usersession := UserSessions[uuid] return usersession, http.StatusOK, nil } - -func GetRealmsFromPVE(config *common.Config) map[string]Realm { - realms := map[string]Realm{} - - HTTPClient := http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{}, - }, - } - token := fmt.Sprintf(`%s@%s!%s`, config.PVE.Token.User, config.PVE.Token.Realm, config.PVE.Token.ID) - client := proxmox.NewClient(config.PVE.URL, - proxmox.WithHTTPClient(&HTTPClient), - proxmox.WithAPIToken(token, config.PVE.Token.UUID), - ) - - pverealms, err := client.Domains(context.Background()) - if err != nil { - // failure to get realms is a fatal error - log.Fatalf("Error getting authentication realms: %s", err.Error()) - } - - // add required pve realm handler, removing the pve api token - pveconfig := common.PVEConfig{ - URL: config.PVE.URL, - PAASClientRole: config.PVE.PAASClientRole, - } - realms["pve"] = Realm{ - Type: "pve", - Config: pveconfig, - } - log.Printf("Configured default authentication realm pve") - - // iterate through handlers and add to realms - for _, r := range pverealms { - realm, err := client.Domain(context.Background(), r.Realm) - if err != nil { - log.Printf("Error getting authentication realm %s: %s", r.Realm, err.Error()) - } - - if realm.Type == "ldap" { - ldapconfig := common.LDAPConfig{ - BaseDN: realm.BaseDN, - Hostname: realm.Server1, - TLS: realm.Mode == "ldaps", - StartTLS: realm.Mode == "ldap+starttls", - Verify: bool(realm.Verify), - } - realms[realm.Realm] = Realm{ - Type: realm.Type, - Config: ldapconfig, - } - log.Printf("Configured external authentication realm %s", realm.Realm) - } else { - continue - } - } - - return realms -} diff --git a/app/operations.go b/app/operations.go index 5998eaa..f043ff3 100644 --- a/app/operations.go +++ b/app/operations.go @@ -3,7 +3,7 @@ package app import ( common "access-manager-api/app/common" "access-manager-api/app/ldap" - proxmox "access-manager-api/app/pve" + "access-manager-api/app/pve" "fmt" "net/http" ) @@ -105,7 +105,7 @@ func NewGroup(backends *UserSession, groupname common.Groupname, group common.Gr if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group") } @@ -120,7 +120,7 @@ func ModGroup(backends *UserSession, groupname common.Groupname, group common.Gr if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group") } @@ -189,7 +189,7 @@ func DelGroup(backends *UserSession, groupname common.Groupname) (int, error) { if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group") } @@ -214,7 +214,7 @@ func NewUser(backends *UserSession, username common.Username, user common.User) if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user") } @@ -229,9 +229,7 @@ func ModUser(backends *UserSession, username common.Username, user common.User) if err != nil { return code, err } - // todo, most users will not have access to sync realms, but should be able to modify their own user - // will probably use priviledge escalation to give priviledge for modify user operations - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user") } @@ -265,7 +263,7 @@ func DelUser(backends *UserSession, username common.Username) (int, error) { if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user") } @@ -286,7 +284,7 @@ func AddUserToGroup(backends *UserSession, username common.Username, groupname c if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { // req user in proxmox and req group in realm (not possible to do) return http.StatusUnauthorized, fmt.Errorf("cannot add %s to %s", username.ToString(), groupname.ToString()) } @@ -307,7 +305,7 @@ func DelUserFromGroup(backends *UserSession, username common.Username, groupname if err != nil { return code, err } - return backends.PVE.(proxmox.ProxmoxClient).SyncRealms() + return RootSession.PVE.(pve.ProxmoxClient).SyncRealms() } else { // req user in proxmox and req group in realm (not possible to do) return http.StatusUnauthorized, fmt.Errorf("cannot delete %s from %s", username.ToString(), groupname.ToString()) } diff --git a/app/pve/pve.go b/app/pve/pve.go index 101d166..2b446ba 100644 --- a/app/pve/pve.go +++ b/app/pve/pve.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "fmt" + "log" "net/http" "slices" "strings" @@ -50,6 +51,29 @@ func NewClientFromCredentials(config common.PVEConfig, username common.Username, return ProxmoxClient{config: &config, client: client}, http.StatusOK, nil } +// creates a new client binding with associated permissions +func NewClientFromAPIToken(config common.PVEConfig) (common.Backend, int, error) { + HTTPClient := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{}, + }, + } + token := fmt.Sprintf(`%s@%s!%s`, config.Token.User, config.Token.Realm, config.Token.ID) + client := proxmox.NewClient(config.URL, + proxmox.WithHTTPClient(&HTTPClient), + proxmox.WithAPIToken(token, config.Token.UUID), + ) + + // check that the user is authenticated because proxmox.NewClient does not return an error + // version route is accessible to any authenticated user + _, err := client.Version(context.Background()) + if err != nil { // could not get version so therefore the user is not authenticated + return nil, http.StatusUnauthorized, err + } + + return ProxmoxClient{config: &config, client: client}, http.StatusOK, nil +} + func (pve ProxmoxClient) SyncRealms() (int, error) { domains, err := pve.client.Domains(context.Background()) if proxmox.IsNotAuthorized(err) { @@ -77,6 +101,54 @@ func (pve ProxmoxClient) SyncRealms() (int, error) { return http.StatusOK, nil } +func (pve ProxmoxClient) GetRealms() map[string]common.Realm { + realms := map[string]common.Realm{} + + pverealms, err := pve.client.Domains(context.Background()) + if err != nil { + // failure to get realms is a fatal error + log.Fatalf("Error getting authentication realms: %s", err.Error()) + } + + // add required pve realm handler, removing the pve api token + pveconfig := common.PVEConfig{ + URL: pve.config.URL, + PAASClientRole: pve.config.PAASClientRole, + } + realms["pve"] = common.Realm{ + Type: "pve", + Config: pveconfig, + } + log.Printf("Configured default authentication realm pve") + + // iterate through handlers and add to realms + for _, r := range pverealms { + realm, err := pve.client.Domain(context.Background(), r.Realm) + if err != nil { + log.Printf("Error getting authentication realm %s: %s", r.Realm, err.Error()) + } + + if realm.Type == "ldap" { + ldapconfig := common.LDAPConfig{ + BaseDN: realm.BaseDN, + Hostname: realm.Server1, + TLS: realm.Mode == "ldaps", + StartTLS: realm.Mode == "ldap+starttls", + Verify: bool(realm.Verify), + } + realms[realm.Realm] = common.Realm{ + Type: realm.Type, + Config: ldapconfig, + } + log.Printf("Configured external authentication realm %s", realm.Realm) + } else { + continue + } + } + + return realms +} + func (pve ProxmoxClient) NewPool(poolname string, pool common.Pool) (int, error) { err := pve.client.NewPool(context.Background(), poolname, "") if proxmox.IsNotAuthorized(err) {