mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-19 07:50:43 +01:00
- Add freebsd-amd64 and freebsd-arm64 to normalizeUnifiedAgentArch() so the download endpoint serves FreeBSD binaries when requested - Add FreeBSD/pfSense/OPNsense platform option to agent setup UI with note about bash installation requirement - Add FreeBSD test cases to unified_agent_test.go Fixes installation on pfSense/OPNsense where users were getting 404 errors because the backend didn't recognize the freebsd-amd64 arch parameter from install.sh.
199 lines
6.1 KiB
Go
199 lines
6.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Permission defines an access rule for an action on a resource.
|
|
type Permission struct {
|
|
Action string `json:"action"` // read, write, delete, admin
|
|
Resource string `json:"resource"` // nodes, nodes:pve1, settings, *
|
|
Effect string `json:"effect,omitempty"` // "allow" (default) or "deny"
|
|
Conditions map[string]string `json:"conditions,omitempty"` // ABAC conditions, e.g., {"tag": "production"}
|
|
}
|
|
|
|
// EffectAllow and EffectDeny are the valid values for Permission.Effect
|
|
const (
|
|
EffectAllow = "allow"
|
|
EffectDeny = "deny"
|
|
)
|
|
|
|
// GetEffect returns the effect, defaulting to "allow" if empty
|
|
func (p Permission) GetEffect() string {
|
|
if p.Effect == "" {
|
|
return EffectAllow
|
|
}
|
|
return p.Effect
|
|
}
|
|
|
|
// Role represents a collection of permissions.
|
|
type Role struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
ParentID string `json:"parentId,omitempty"` // For role inheritance
|
|
Permissions []Permission `json:"permissions"`
|
|
IsBuiltIn bool `json:"isBuiltIn"`
|
|
Priority int `json:"priority,omitempty"` // For conflict resolution (higher = more priority)
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// UserRoleAssignment maps a user to one or more roles.
|
|
type UserRoleAssignment struct {
|
|
Username string `json:"username"`
|
|
RoleIDs []string `json:"roleIds"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// RBACChangeLog represents an audit entry for RBAC changes.
|
|
type RBACChangeLog struct {
|
|
ID string `json:"id"`
|
|
Action string `json:"action"` // role_created, role_updated, role_deleted, user_assigned, etc.
|
|
EntityType string `json:"entityType"` // role, assignment
|
|
EntityID string `json:"entityId"` // Role ID or username
|
|
OldValue string `json:"oldValue,omitempty"` // JSON of previous state
|
|
NewValue string `json:"newValue,omitempty"` // JSON of new state
|
|
User string `json:"user,omitempty"` // Who made the change
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// RBAC change action constants
|
|
const (
|
|
ActionRoleCreated = "role_created"
|
|
ActionRoleUpdated = "role_updated"
|
|
ActionRoleDeleted = "role_deleted"
|
|
ActionUserAssigned = "user_assigned"
|
|
ActionUserUnassigned = "user_unassigned"
|
|
ActionUserRolesUpdate = "user_roles_updated"
|
|
)
|
|
|
|
// Built-in Role IDs
|
|
const (
|
|
RoleAdmin = "admin"
|
|
RoleOperator = "operator"
|
|
RoleViewer = "viewer"
|
|
RoleAuditor = "auditor"
|
|
RoleCompliance = "compliance" // Alias for Auditor
|
|
)
|
|
|
|
// Manager defines the interface for managing RBAC data.
|
|
// This is typically implemented by the enterprise RBAC store.
|
|
type Manager interface {
|
|
// Role management
|
|
GetRoles() []Role
|
|
GetRole(id string) (Role, bool)
|
|
SaveRole(role Role) error
|
|
DeleteRole(id string) error
|
|
|
|
// Assignment management
|
|
GetUserAssignments() []UserRoleAssignment
|
|
GetUserAssignment(username string) (UserRoleAssignment, bool)
|
|
AssignRole(username string, roleID string) error
|
|
UpdateUserRoles(username string, roleIDs []string) error
|
|
RemoveRole(username string, roleID string) error
|
|
|
|
// Effective permissions
|
|
GetUserPermissions(username string) []Permission
|
|
}
|
|
|
|
// ExtendedManager extends Manager with advanced RBAC features.
|
|
// This is implemented by the SQLite-backed manager for Pro features.
|
|
type ExtendedManager interface {
|
|
Manager
|
|
|
|
// Role inheritance
|
|
GetRoleWithInheritance(id string) (Role, []Permission, bool) // Returns role and all inherited permissions
|
|
GetRolesWithInheritance(username string) []Role // Returns user's roles with inheritance chain
|
|
|
|
// Change log
|
|
GetChangeLogs(limit int, offset int) []RBACChangeLog
|
|
GetChangeLogsForEntity(entityType, entityID string) []RBACChangeLog
|
|
|
|
// Context-aware operations (for audit trail)
|
|
SaveRoleWithContext(role Role, username string) error
|
|
DeleteRoleWithContext(id string, username string) error
|
|
UpdateUserRolesWithContext(username string, roleIDs []string, byUser string) error
|
|
}
|
|
|
|
var (
|
|
globalManager Manager
|
|
managerMu sync.RWMutex
|
|
)
|
|
|
|
// SetManager sets the global RBAC manager instance.
|
|
// This should be called during application initialization.
|
|
func SetManager(m Manager) {
|
|
managerMu.Lock()
|
|
defer managerMu.Unlock()
|
|
globalManager = m
|
|
}
|
|
|
|
// GetManager returns the global RBAC manager instance.
|
|
func GetManager() Manager {
|
|
managerMu.RLock()
|
|
defer managerMu.RUnlock()
|
|
return globalManager
|
|
}
|
|
|
|
// Requirement helpers
|
|
func HasPermission(ctx context.Context, action, resource string) bool {
|
|
authorizer := GetAuthorizer()
|
|
allowed, _ := authorizer.Authorize(ctx, action, resource)
|
|
return allowed
|
|
}
|
|
|
|
// GetExtendedManager returns the global manager as ExtendedManager if it implements the interface.
|
|
func GetExtendedManager() ExtendedManager {
|
|
managerMu.RLock()
|
|
defer managerMu.RUnlock()
|
|
if em, ok := globalManager.(ExtendedManager); ok {
|
|
return em
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MatchesResource checks if a permission's resource pattern matches a requested resource.
|
|
// Supports:
|
|
// - Exact match: "nodes" matches "nodes"
|
|
// - Specific ID: "nodes:pve1" matches "nodes:pve1"
|
|
// - Wildcard: "nodes:*" matches "nodes:pve1"
|
|
// - Global wildcard: "*" matches any resource
|
|
func MatchesResource(pattern, resource string) bool {
|
|
// Global wildcard matches everything
|
|
if pattern == "*" {
|
|
return true
|
|
}
|
|
|
|
// Exact match
|
|
if pattern == resource {
|
|
return true
|
|
}
|
|
|
|
// Check for wildcard pattern (e.g., "nodes:*")
|
|
if len(pattern) > 2 && pattern[len(pattern)-2:] == ":*" {
|
|
prefix := pattern[:len(pattern)-2]
|
|
// Match if resource starts with the prefix
|
|
if resource == prefix {
|
|
return true
|
|
}
|
|
// Match if resource has the prefix followed by a colon
|
|
if len(resource) > len(prefix) && resource[:len(prefix)+1] == prefix+":" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// MatchesAction checks if a permission's action matches a requested action.
|
|
// "admin" action matches any action.
|
|
func MatchesAction(permAction, requestedAction string) bool {
|
|
if permAction == "admin" {
|
|
return true
|
|
}
|
|
return permAction == requestedAction
|
|
}
|