Merge Go frontend (apiserver + xcecli) into meson build
This commit is contained in:
parent
43dc12a36c
commit
c9dc8c4676
205 changed files with 30793 additions and 328 deletions
416
src/frontend/apiserver/controllers/rbac_check.go
Normal file
416
src/frontend/apiserver/controllers/rbac_check.go
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"apiserver/common"
|
||||
"apiserver/models"
|
||||
"apiserver/store"
|
||||
"apiserver/utils"
|
||||
logger "apiserver/utils"
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type RBACConfig struct {
|
||||
Permissions []string `yaml:"permissions"`
|
||||
Roles map[string]Role `yaml:"roles"`
|
||||
Users map[string]User `yaml:"users"`
|
||||
}
|
||||
|
||||
// define Role
|
||||
type Role struct {
|
||||
Includes []string `yaml:"includes,omitempty"`
|
||||
Permissions []Permission `yaml:"permissions"`
|
||||
}
|
||||
type Permission struct {
|
||||
Action string `yaml:"action"`
|
||||
Domain []Domain `yaml:"domain"`
|
||||
}
|
||||
|
||||
// define User
|
||||
type User struct {
|
||||
Roles []UserRole `yaml:"roles"`
|
||||
}
|
||||
type UserRole struct {
|
||||
Name string `yaml:"name"`
|
||||
Domain []Domain `yaml:"domain"`
|
||||
}
|
||||
type Domain struct {
|
||||
Type string `yaml:"type"`
|
||||
Values []string `yaml:"values,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
rbacConfig RBACConfig
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func LoadRBACConfig(path string) error {
|
||||
logger.Debug("Enter LoadRBACConfig() function")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := yaml.Unmarshal(data, &rbacConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Populate store.Roles from parsed config
|
||||
for roleName, role := range rbacConfig.Roles {
|
||||
var perms []store.RolePermission
|
||||
for _, perm := range role.Permissions {
|
||||
for _, domain := range perm.Domain {
|
||||
perms = append(perms, store.RolePermission{
|
||||
Action: perm.Action,
|
||||
DomainType: domain.Type,
|
||||
DomainValues: domain.Values,
|
||||
})
|
||||
}
|
||||
}
|
||||
store.AddRoleNames(roleName)
|
||||
if len(perms) > 0 {
|
||||
store.AddPermissionsForRole(roleName, perms)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve role inheritance (includes)
|
||||
resolveRoleIncludes()
|
||||
|
||||
// Load user-role mappings from users section
|
||||
for userName, user := range rbacConfig.Users {
|
||||
for _, role := range user.Roles {
|
||||
store.AddRoleForUser(userName, role.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveRoleIncludes() {
|
||||
for roleName, rbacRole := range rbacConfig.Roles {
|
||||
if len(rbacRole.Includes) == 0 {
|
||||
continue
|
||||
}
|
||||
inherited := collectRolePermissions(
|
||||
UserRole{Name: roleName},
|
||||
rbacConfig.Roles,
|
||||
make(map[string]struct{}),
|
||||
)
|
||||
for _, perm := range inherited {
|
||||
for _, domain := range perm.Domain {
|
||||
store.AddPermissionsForRole(roleName, []store.RolePermission{
|
||||
{Action: perm.Action, DomainType: domain.Type, DomainValues: domain.Values},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validate token and check permission
|
||||
func RequirePermission(perm string) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
logger.Debug("Enter RequirePermission() function", zap.String("perm", perm))
|
||||
token := ctx.GetHeader("Authorization")
|
||||
logonUser, err := GetUsernameFromToken(token)
|
||||
if err != nil {
|
||||
ReturnError(ctx, common.ERROR_CODE, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
logger.Debug("Logon user is ", zap.String("logonUser", logonUser))
|
||||
// check if user has cluster_admin role
|
||||
userRoles := store.GetUserRoleNames(logonUser)
|
||||
if utils.ContainsField(userRoles, "cluster_admin") {
|
||||
return
|
||||
}
|
||||
permsWithType := GetUserAllPermissionsWithType(logonUser)
|
||||
logger.Debug("In RequirePermission after call GetUserAllPermissionsWithType from cache",
|
||||
zap.Any("permsWithType", permsWithType))
|
||||
var allPerms []string
|
||||
for _, perm := range permsWithType {
|
||||
allPerms = append(allPerms, perm.Permission)
|
||||
}
|
||||
found := false
|
||||
for _, p := range allPerms {
|
||||
if p == perm {
|
||||
found = true
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Error("Permission denied", zap.String("perm", perm), zap.String("logonUser", logonUser))
|
||||
ReturnError(ctx, common.ERROR_AUTH_CODE, common.ERROR_ACCESS_FORBIDDEN_MSG, nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get all permissions from rbac.yaml file
|
||||
func GetAllPermissions() []string {
|
||||
permissions := rbacConfig.Permissions
|
||||
return permissions
|
||||
}
|
||||
|
||||
// get role permissions
|
||||
func GetBuiltinRolePermissions(roleName string) []Permission {
|
||||
role, ok := rbacConfig.Roles[roleName]
|
||||
if !ok {
|
||||
return []Permission{}
|
||||
}
|
||||
return role.Permissions
|
||||
}
|
||||
|
||||
// get all user permission,
|
||||
// if user not exist in store, use basic_user permission
|
||||
func GetUserAllPermissionsWithType(userName string) []models.RolePermission {
|
||||
userRoles := store.GetUserRoleNames(userName)
|
||||
if len(userRoles) == 0 {
|
||||
// case 1: user not in store, use basic_user
|
||||
builtInPerms := GetBuiltinRolePermissions("basic_user")
|
||||
permissionTransform := make([]models.RolePermission, 0)
|
||||
for _, p := range builtInPerms {
|
||||
for _, domain := range p.Domain {
|
||||
rp := models.RolePermission{
|
||||
Permission: p.Action,
|
||||
DomainType: domain.Type,
|
||||
DomainValues: domain.Values,
|
||||
}
|
||||
permissionTransform = append(permissionTransform, rp)
|
||||
}
|
||||
}
|
||||
logger.Debug("case 1, assign basic_user permission to unregistered user")
|
||||
return permissionTransform
|
||||
}
|
||||
|
||||
builtInRoles := utils.IncludeBuiltinRoles(userRoles)
|
||||
var permissionListsType []models.RolePermission
|
||||
if len(builtInRoles) > 0 {
|
||||
permissionTransform := make([]models.RolePermission, 0)
|
||||
for _, role := range builtInRoles {
|
||||
builtinPerms := GetBuiltinRolePermissions(role)
|
||||
for _, p := range builtinPerms {
|
||||
for _, domain := range p.Domain {
|
||||
rp := models.RolePermission{
|
||||
Permission: p.Action,
|
||||
DomainType: domain.Type,
|
||||
DomainValues: domain.Values,
|
||||
}
|
||||
permissionTransform = append(permissionTransform, rp)
|
||||
}
|
||||
}
|
||||
}
|
||||
permissionListsType = append(permissionListsType, permissionTransform...)
|
||||
logger.Debug("case 2, builtin role permissions", zap.Any("permissionListsType", permissionListsType))
|
||||
}
|
||||
|
||||
for _, userRole := range userRoles {
|
||||
permissionListsType = append(permissionListsType, GetRoleAllPermissionsWithType(userRole)...)
|
||||
}
|
||||
logger.Debug("case 3, before remove duplicated permissions", zap.Any("permissionListsType", permissionListsType))
|
||||
permissionListsType = DeduplicatePermissions(permissionListsType)
|
||||
logger.Debug("case 4 DeduplicatePermissions", zap.Any("permissionListsType", permissionListsType))
|
||||
return permissionListsType
|
||||
}
|
||||
|
||||
func GetRoleAllPermissionsWithType(roleName string) []models.RolePermission {
|
||||
var permissionsType []models.RolePermission
|
||||
perms := store.GetRolePermissions(roleName)
|
||||
for _, perm := range perms {
|
||||
permissionsType = append(permissionsType, models.RolePermission{
|
||||
Permission: perm.Action,
|
||||
DomainType: perm.DomainType,
|
||||
DomainValues: perm.DomainValues,
|
||||
})
|
||||
}
|
||||
return permissionsType
|
||||
}
|
||||
|
||||
// DeduplicatePermissions removes duplicate permissions based on the specified rules:
|
||||
// - If permission name and domain type are the same, keep only one
|
||||
// - If domain values contain "all", keep the one without "all" when both exist
|
||||
// DeduplicatePermissions removes duplicate permissions based on the specified rules:
|
||||
// - If permission names are the same, keep only one
|
||||
// - If one has specific domain values and another has "all" domain type, keep the specific one
|
||||
func DeduplicatePermissions(permissions []models.RolePermission) []models.RolePermission {
|
||||
permissionMap := make(map[string]models.RolePermission) // key: permission_name
|
||||
for _, perm := range permissions {
|
||||
if existingPerm, exists := permissionMap[perm.Permission]; exists {
|
||||
// Check if one has more specific domain than the other
|
||||
existingHasAll := (existingPerm.DomainType == models.DomainTypeAll)
|
||||
currentHasAll := (perm.DomainType == models.DomainTypeAll)
|
||||
|
||||
// If existing has "all" and current has specific domain, replace it
|
||||
if existingHasAll && !currentHasAll {
|
||||
permissionMap[perm.Permission] = perm
|
||||
} else if !existingHasAll && currentHasAll {
|
||||
// Keep existing specific domain, discard current "all"
|
||||
// No action needed
|
||||
} else if existingHasAll && currentHasAll {
|
||||
// Both have "all", keep existing
|
||||
// No action needed
|
||||
} else {
|
||||
// Both have specific domains, check if current has values and existing doesn't
|
||||
existingHasValues := len(existingPerm.DomainValues) > 0
|
||||
currentHasValues := len(perm.DomainValues) > 0
|
||||
|
||||
if !existingHasValues && currentHasValues {
|
||||
permissionMap[perm.Permission] = perm
|
||||
} else if existingHasValues && !currentHasValues {
|
||||
// Keep existing with values
|
||||
// No action needed
|
||||
} else {
|
||||
// Both have values or both don't have values, merge or keep existing
|
||||
mergedValues := mergeDomainValues(existingPerm.DomainValues, perm.DomainValues)
|
||||
permissionMap[perm.Permission] = models.RolePermission{
|
||||
Permission: existingPerm.Permission,
|
||||
DomainType: existingPerm.DomainType,
|
||||
DomainValues: mergedValues,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
permissionMap[perm.Permission] = perm
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map back to slice
|
||||
result := make([]models.RolePermission, 0, len(permissionMap))
|
||||
for _, perm := range permissionMap {
|
||||
result = append(result, perm)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to check if domain values contain "all"
|
||||
func hasAllDomain(domainValues []string) bool {
|
||||
for _, value := range domainValues {
|
||||
if value == models.DomainTypeAll {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func mergeDomainValues(values1, values2 []string) []string {
|
||||
// Use a map to track unique values
|
||||
valueMap := make(map[string]bool)
|
||||
|
||||
// Add all values from first slice
|
||||
for _, v := range values1 {
|
||||
valueMap[v] = true
|
||||
}
|
||||
|
||||
// Add all values from second slice
|
||||
for _, v := range values2 {
|
||||
valueMap[v] = true
|
||||
}
|
||||
|
||||
// Convert back to slice
|
||||
merged := make([]string, 0, len(valueMap))
|
||||
for v := range valueMap {
|
||||
merged = append(merged, v)
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
func GetRequireResourcePermission(logonUser string, permId string) (models.RolePermission, error) {
|
||||
logger.Debug("Enter GetRequireResourcePermission() function",
|
||||
zap.Any("logonUser", logonUser), zap.Any("permId", permId))
|
||||
availablePerm := models.RolePermission{}
|
||||
|
||||
perms := GetUserAllPermissionsWithType(logonUser)
|
||||
logger.Debug("In GetRequireResourcePermission function user permissions are:",
|
||||
zap.Any("perms", perms))
|
||||
if len(perms) == 0 {
|
||||
return availablePerm, errors.New("no permission")
|
||||
}
|
||||
|
||||
for _, perm := range perms {
|
||||
if perm.Permission != permId {
|
||||
continue
|
||||
}
|
||||
availablePerm.Permission = perm.Permission
|
||||
availablePerm.DomainType = perm.DomainType
|
||||
availablePerm.DomainValues = perm.DomainValues
|
||||
return availablePerm, nil
|
||||
}
|
||||
|
||||
return availablePerm, errors.New("no permission")
|
||||
}
|
||||
func collectRolePermissions(userRole UserRole, rbacRoles map[string]Role, visited map[string]struct{}) []Permission {
|
||||
var perms []Permission
|
||||
var collect func(roleName string, userDomains []Domain)
|
||||
collect = func(roleName string, userDomains []Domain) {
|
||||
if _, ok := visited[roleName]; ok {
|
||||
return
|
||||
}
|
||||
visited[roleName] = struct{}{}
|
||||
role, ok := rbacRoles[roleName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, perm := range role.Permissions {
|
||||
mergedDomains := mergeDomains(perm.Domain, userDomains)
|
||||
if mergedDomains == nil {
|
||||
mergedDomains = []Domain{{Type: "all"}} // default to all if no
|
||||
}
|
||||
perms = append(perms, Permission{
|
||||
Action: perm.Action,
|
||||
Domain: mergedDomains,
|
||||
})
|
||||
}
|
||||
for _, inc := range role.Includes {
|
||||
collect(inc, userDomains)
|
||||
}
|
||||
}
|
||||
collect(userRole.Name, userRole.Domain)
|
||||
return perms
|
||||
}
|
||||
func mergeDomains(roleDomains, userDomains []Domain) []Domain {
|
||||
domainMap := make(map[string]map[string]struct{})
|
||||
if len(userDomains) == 1 && userDomains[0].Type == "all" {
|
||||
return roleDomains
|
||||
}
|
||||
if len(roleDomains) == 1 && roleDomains[0].Type == "all" {
|
||||
return userDomains
|
||||
}
|
||||
|
||||
for _, d := range roleDomains {
|
||||
if d.Type == "all" {
|
||||
continue
|
||||
}
|
||||
if _, ok := domainMap[d.Type]; !ok {
|
||||
domainMap[d.Type] = make(map[string]struct{})
|
||||
}
|
||||
for _, v := range d.Values {
|
||||
domainMap[d.Type][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, d := range userDomains {
|
||||
if d.Type == "all" {
|
||||
continue
|
||||
}
|
||||
if _, ok := domainMap[d.Type]; !ok {
|
||||
domainMap[d.Type] = make(map[string]struct{})
|
||||
}
|
||||
for _, v := range d.Values {
|
||||
domainMap[d.Type][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
var merged []Domain
|
||||
for t, vals := range domainMap {
|
||||
var values []string
|
||||
for v := range vals {
|
||||
values = append(values, v)
|
||||
}
|
||||
merged = append(merged, Domain{Type: t, Values: values})
|
||||
}
|
||||
return merged
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue