397 lines
9.6 KiB
Go
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
|
|
}
|