fix issue with user realm sync and insufficient permissions using priviledge escalation
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 13s
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 13s
This commit is contained in:
@@ -2,6 +2,11 @@ package app
|
|||||||
|
|
||||||
import paas "proxmoxaas-common-lib"
|
import paas "proxmoxaas-common-lib"
|
||||||
|
|
||||||
|
type Realm struct {
|
||||||
|
Type string
|
||||||
|
Config any
|
||||||
|
}
|
||||||
|
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
NewPool(poolname string, pool Pool) (int, error)
|
NewPool(poolname string, pool Pool) (int, error)
|
||||||
ModPool(poolname string, pool Pool) (int, error)
|
ModPool(poolname string, pool Pool) (int, error)
|
||||||
|
|||||||
+15
-71
@@ -1,9 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -11,18 +9,13 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
common "access-manager-api/app/common"
|
common "access-manager-api/app/common"
|
||||||
|
"access-manager-api/app/pve"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/luthermonson/go-proxmox"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Realm struct {
|
|
||||||
Type string
|
|
||||||
Config any
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserSession struct {
|
type UserSession struct {
|
||||||
PVE common.Backend
|
PVE common.Backend
|
||||||
Realm struct {
|
Realm struct {
|
||||||
@@ -34,8 +27,9 @@ type UserSession struct {
|
|||||||
|
|
||||||
var Version = "1.0.0"
|
var Version = "1.0.0"
|
||||||
var Config common.Config
|
var Config common.Config
|
||||||
|
var RootSession *UserSession
|
||||||
var UserSessions map[string]*UserSession
|
var UserSessions map[string]*UserSession
|
||||||
var Realms map[string]Realm
|
var Realms map[string]common.Realm
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
configPath := flag.String("config", "config.json", "path to config.json file")
|
configPath := flag.String("config", "config.json", "path to config.json file")
|
||||||
@@ -53,9 +47,18 @@ func Run() {
|
|||||||
// setup api auth cookies
|
// setup api auth cookies
|
||||||
SetupAPISessionStore(router, &Config)
|
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
|
// get realms from proxmox
|
||||||
Realms = make(map[string]Realm)
|
Realms = make(map[string]common.Realm)
|
||||||
Realms = GetRealmsFromPVE(&Config)
|
Realms = RootSession.PVE.(pve.ProxmoxClient).GetRealms()
|
||||||
|
|
||||||
// make global session map
|
// make global session map
|
||||||
UserSessions = make(map[string]*UserSession)
|
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))
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Error starting router: %s", err.Error())
|
log.Fatalf("Error starting router: %s", err.Error())
|
||||||
}
|
}
|
||||||
@@ -118,62 +121,3 @@ func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) {
|
|||||||
usersession := UserSessions[uuid]
|
usersession := UserSessions[uuid]
|
||||||
return usersession, http.StatusOK, nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
+9
-11
@@ -3,7 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
common "access-manager-api/app/common"
|
common "access-manager-api/app/common"
|
||||||
"access-manager-api/app/ldap"
|
"access-manager-api/app/ldap"
|
||||||
proxmox "access-manager-api/app/pve"
|
"access-manager-api/app/pve"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -105,7 +105,7 @@ func NewGroup(backends *UserSession, groupname common.Groupname, group common.Gr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
// todo, most users will not have access to sync realms, but should be able to modify their own user
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
// will probably use priviledge escalation to give priviledge for modify user operations
|
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
|
return RootSession.PVE.(pve.ProxmoxClient).SyncRealms()
|
||||||
} else {
|
} else {
|
||||||
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
|
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 {
|
if err != nil {
|
||||||
return code, err
|
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)
|
} 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())
|
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 {
|
if err != nil {
|
||||||
return code, err
|
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)
|
} 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())
|
return http.StatusUnauthorized, fmt.Errorf("cannot delete %s from %s", username.ToString(), groupname.ToString())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -50,6 +51,29 @@ func NewClientFromCredentials(config common.PVEConfig, username common.Username,
|
|||||||
return ProxmoxClient{config: &config, client: client}, http.StatusOK, nil
|
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) {
|
func (pve ProxmoxClient) SyncRealms() (int, error) {
|
||||||
domains, err := pve.client.Domains(context.Background())
|
domains, err := pve.client.Domains(context.Background())
|
||||||
if proxmox.IsNotAuthorized(err) {
|
if proxmox.IsNotAuthorized(err) {
|
||||||
@@ -77,6 +101,54 @@ func (pve ProxmoxClient) SyncRealms() (int, error) {
|
|||||||
return http.StatusOK, nil
|
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) {
|
func (pve ProxmoxClient) NewPool(poolname string, pool common.Pool) (int, error) {
|
||||||
err := pve.client.NewPool(context.Background(), poolname, "")
|
err := pve.client.NewPool(context.Background(), poolname, "")
|
||||||
if proxmox.IsNotAuthorized(err) {
|
if proxmox.IsNotAuthorized(err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user