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 { perms, fromRoles := collectUserPermissions(userName) if !fromRoles { return perms } permissionListsType := DeduplicatePermissions(perms) logger.Debug("case 4 DeduplicatePermissions", zap.Any("permissionListsType", permissionListsType)) return permissionListsType } // collectUserPermissionsWithType returns every grant of the user, one entry // per scope, before DeduplicatePermissions (bug-1934: job scopes add up). func collectUserPermissionsWithType(userName string) []models.RolePermission { perms, _ := collectUserPermissions(userName) return perms } // collectUserPermissions returns the user's grants; fromRoles is false when // the user has no roles and gets the builtin basic_user grants. func collectUserPermissions(userName string) ([]models.RolePermission, bool) { 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, false } 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)) return permissionListsType, true } 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 }