deb-bootc-compose/internal/security/rbac.go
2025-08-18 23:32:51 -07:00

397 lines
9.6 KiB
Go

package security
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
)
// RBACManager manages role-based access control
type RBACManager struct {
config *RBACConfig
roles map[string]*RoleConfig
policies map[string]*PolicyConfig
logger *logrus.Logger
}
// NewRBACManager creates a new RBAC manager
func NewRBACManager(config *RBACConfig) (*RBACManager, error) {
rbac := &RBACManager{
config: config,
roles: make(map[string]*RoleConfig),
policies: make(map[string]*PolicyConfig),
logger: logrus.New(),
}
// Load roles
for roleName, roleConfig := range config.Roles {
rbac.roles[roleName] = &roleConfig
}
// Load policies
for policyName, policyConfig := range config.Policies {
rbac.policies[policyName] = &policyConfig
}
// Validate role inheritance
if err := rbac.validateRoleInheritance(); err != nil {
return nil, fmt.Errorf("invalid role inheritance: %w", err)
}
return rbac, nil
}
// validateRoleInheritance validates that role inheritance is acyclic
func (rbac *RBACManager) validateRoleInheritance() error {
visited := make(map[string]bool)
temp := make(map[string]bool)
var visit func(string) error
visit = func(roleName string) error {
if temp[roleName] {
return fmt.Errorf("circular inheritance detected for role %s", roleName)
}
if visited[roleName] {
return nil
}
temp[roleName] = true
role, exists := rbac.roles[roleName]
if !exists {
return fmt.Errorf("role %s not found", roleName)
}
// Visit inherited roles
for _, inheritedRole := range role.Inherits {
if err := visit(inheritedRole); err != nil {
return err
}
}
temp[roleName] = false
visited[roleName] = true
return nil
}
// Visit all roles
for roleName := range rbac.roles {
if !visited[roleName] {
if err := visit(roleName); err != nil {
return err
}
}
}
return nil
}
// GetRole returns a role by name
func (rbac *RBACManager) GetRole(roleName string) (*RoleConfig, bool) {
role, exists := rbac.roles[roleName]
return role, exists
}
// GetUserRoles returns all roles for a user
func (rbac *RBACManager) GetUserRoles(user *UserInfo) []string {
roles := make([]string, 0)
// Add explicit roles from user
roles = append(roles, user.Groups...)
// Add default role if specified
if rbac.config.DefaultRole != "" {
roles = append(roles, rbac.config.DefaultRole)
}
return roles
}
// GetEffectivePermissions returns all permissions for a user including inherited ones
func (rbac *RBACManager) GetEffectivePermissions(user *UserInfo) []string {
permissions := make(map[string]bool)
visited := make(map[string]bool)
// Get user roles
userRoles := rbac.GetUserRoles(user)
// Collect permissions from all roles
for _, roleName := range userRoles {
rbac.collectRolePermissions(roleName, permissions, visited)
}
// Convert to slice
result := make([]string, 0, len(permissions))
for permission := range permissions {
result = append(result, permission)
}
return result
}
// collectRolePermissions recursively collects permissions from a role and its inherited roles
func (rbac *RBACManager) collectRolePermissions(roleName string, permissions map[string]bool, visited map[string]bool) {
if visited[roleName] {
return
}
visited[roleName] = true
role, exists := rbac.roles[roleName]
if !exists {
return
}
// Add direct permissions
for _, permission := range role.Permissions {
permissions[permission] = true
}
// Add inherited permissions
for _, inheritedRole := range role.Inherits {
rbac.collectRolePermissions(inheritedRole, permissions, visited)
}
}
// Authorize checks if a user has permission to perform an action on a resource
func (rbac *RBACManager) Authorize(user *UserInfo, resource, action string) bool {
// Get effective permissions
permissions := rbac.GetEffectivePermissions(user)
// Check if user has explicit permission
permissionString := fmt.Sprintf("%s:%s", resource, action)
for _, permission := range permissions {
if rbac.matchPermission(permission, permissionString) {
return true
}
}
// Check policies
return rbac.evaluatePolicies(user, resource, action)
}
// matchPermission checks if a permission pattern matches a permission string
func (rbac *RBACManager) matchPermission(pattern, permission string) bool {
// Exact match
if pattern == permission {
return true
}
// Wildcard match
if strings.HasSuffix(pattern, ":*") {
resource := strings.TrimSuffix(pattern, ":*")
if strings.HasPrefix(permission, resource+":") {
return true
}
}
if strings.HasPrefix(pattern, "*:") {
action := strings.TrimPrefix(pattern, "*:")
if strings.HasSuffix(permission, ":"+action) {
return true
}
}
// Full wildcard
if pattern == "*:*" {
return true
}
return false
}
// evaluatePolicies evaluates all applicable policies for the authorization request
func (rbac *RBACManager) evaluatePolicies(user *UserInfo, resource, action string) bool {
// Default to deny
result := false
for _, policy := range rbac.policies {
if rbac.isPolicyApplicable(policy, user, resource, action) {
// Evaluate policy conditions
if rbac.evaluatePolicyConditions(policy, user, resource, action) {
if policy.Effect == "allow" {
result = true
} else if policy.Effect == "deny" {
result = false
break // Deny overrides allow
}
}
}
}
return result
}
// isPolicyApplicable checks if a policy applies to the given user, resource, and action
func (rbac *RBACManager) isPolicyApplicable(policy *PolicyConfig, user *UserInfo, resource, action string) bool {
// Check if resource matches
if !rbac.matchResource(policy.Resources, resource) {
return false
}
// Check if action matches
if !rbac.matchAction(policy.Actions, action) {
return false
}
return true
}
// matchResource checks if a resource matches any of the policy resources
func (rbac *RBACManager) matchResource(policyResources []string, resource string) bool {
for _, policyResource := range policyResources {
if rbac.matchPattern(policyResource, resource) {
return true
}
}
return false
}
// matchAction checks if an action matches any of the policy actions
func (rbac *RBACManager) matchAction(policyActions []string, action string) bool {
for _, policyAction := range policyActions {
if rbac.matchPattern(policyAction, action) {
return true
}
}
return false
}
// matchPattern checks if a pattern matches a value using glob patterns
func (rbac *RBACManager) matchPattern(pattern, value string) bool {
// Exact match
if pattern == value {
return true
}
// Wildcard match
if strings.Contains(pattern, "*") {
// Simple glob pattern matching
patternParts := strings.Split(pattern, "*")
if len(patternParts) == 1 {
// No wildcard
return pattern == value
}
// Check if value starts with first part and ends with last part
if !strings.HasPrefix(value, patternParts[0]) {
return false
}
if len(patternParts) > 1 && patternParts[len(patternParts)-1] != "" {
if !strings.HasSuffix(value, patternParts[len(patternParts)-1]) {
return false
}
}
return true
}
return false
}
// evaluatePolicyConditions evaluates policy conditions
func (rbac *RBACManager) evaluatePolicyConditions(policy *PolicyConfig, user *UserInfo, resource, action string) bool {
if len(policy.Conditions) == 0 {
return true
}
// Simple condition evaluation
for conditionKey, conditionValue := range policy.Conditions {
switch conditionKey {
case "user_groups":
if groups, ok := conditionValue.([]interface{}); ok {
userHasGroup := false
for _, group := range groups {
if groupStr, ok := group.(string); ok {
for _, userGroup := range user.Groups {
if userGroup == groupStr {
userHasGroup = true
break
}
}
}
}
if !userHasGroup {
return false
}
}
case "time_of_day":
// Could implement time-based conditions here
// For now, always return true
case "ip_range":
// Could implement IP-based conditions here
// For now, always return true
}
}
return true
}
// AddRole adds a new role
func (rbac *RBACManager) AddRole(role *RoleConfig) error {
// Validate role
if role.Name == "" {
return fmt.Errorf("role name is required")
}
// Check for duplicate
if _, exists := rbac.roles[role.Name]; exists {
return fmt.Errorf("role %s already exists", role.Name)
}
// Validate inheritance
for _, inheritedRole := range role.Inherits {
if _, exists := rbac.roles[inheritedRole]; !exists {
return fmt.Errorf("inherited role %s not found", inheritedRole)
}
}
rbac.roles[role.Name] = role
rbac.logger.Infof("Added role: %s", role.Name)
return nil
}
// RemoveRole removes a role
func (rbac *RBACManager) RemoveRole(roleName string) error {
// Check if role exists
if _, exists := rbac.roles[roleName]; !exists {
return fmt.Errorf("role %s not found", roleName)
}
// Check if role is inherited by others
for _, role := range rbac.roles {
for _, inheritedRole := range role.Inherits {
if inheritedRole == roleName {
return fmt.Errorf("cannot remove role %s: it is inherited by role %s", roleName, role.Name)
}
}
}
delete(rbac.roles, roleName)
rbac.logger.Infof("Removed role: %s", roleName)
return nil
}
// UpdateRole updates an existing role
func (rbac *RBACManager) UpdateRole(role *RoleConfig) error {
// Check if role exists
if _, exists := rbac.roles[role.Name]; !exists {
return fmt.Errorf("role %s not found", role.Name)
}
// Validate inheritance
for _, inheritedRole := range role.Inherits {
if _, exists := rbac.roles[inheritedRole]; !exists {
return fmt.Errorf("inherited role %s not found", inheritedRole)
}
}
rbac.roles[role.Name] = role
rbac.logger.Infof("Updated role: %s", role.Name)
return nil
}