Authorization Model

Authorization Model

Skycloak implements a comprehensive Role-Based Access Control (RBAC) system built on top of Keycloak’s authorization services. This model provides fine-grained permission control across the entire platform, from UI components to API endpoints.

Overview

The authorization system is designed around four core concepts:

  • Roles: Define user types (workspace:owner, workspace:admin, workspace:billing, cluster:admin, cluster:viewer)
  • Permissions: Granular actions (cluster:create, user:manage, billing:view)
  • Resources: Protected entities (clusters, applications, workspaces)
  • Policies: Rules that govern access (role-based, attribute-based)

Role Hierarchy

Skycloak implements 5 distinct roles across workspace and cluster scopes:

Workspace Roles

workspace:owner

  • Complete control over the entire workspace
  • Can delete the workspace
  • Can transfer ownership
  • All permissions of workspace:admin
  • Cannot be removed from workspace

workspace:admin

  • Full administrative access to workspace
  • Can invite and manage all workspace members
  • Can assign any role except workspace:owner
  • Access to billing and subscription management
  • Can create, update, and delete all clusters
  • Full application management capabilities
  • Access to event logs and security settings

workspace:billing

  • Specialized role for financial management
  • Can view and manage billing information
  • Can update payment methods
  • Can view subscription details and usage
  • Can download invoices and statements
  • No access to technical resources or user management

Cluster Roles

cluster:admin

  • Full control over assigned clusters
  • Extension management and configuration
  • Direct Keycloak console access
  • Can view and download logs
  • Can manage cluster settings
  • Can create and manage applications
  • Can configure branding and email settings

cluster:viewer

  • Read-only access to cluster information
  • Can view cluster status and health
  • Can view applications and configurations
  • Can access basic metrics and insights
  • Cannot make any modifications
  • Cannot access sensitive logs or console

Permission System

Permission Structure

Permissions follow a hierarchical naming convention:

<resource>:<action>[:<scope>]

Examples:

  • cluster:create - Create new clusters
  • cluster:view:own - View only owned clusters
  • user:manage:workspace - Manage users within workspace
  • billing:view - View billing information

Core Permissions

Cluster Management:

cluster:create          # Create new clusters
cluster:view            # View cluster details
cluster:update          # Modify cluster configuration
cluster:delete          # Delete clusters
cluster:logs            # Access cluster logs
cluster:console         # Access Keycloak console
cluster:extensions      # Manage extensions

Application Management:

application:create      # Create applications
application:view        # View application details
application:update      # Modify applications
application:delete      # Delete applications
application:test        # Test application connections

User Management:

user:create            # Create new users
user:view              # View user profiles
user:update            # Update user information
user:delete            # Delete users
user:invite            # Send invitations
user:manage:roles      # Assign/remove roles

Workspace Management:

workspace:settings     # Modify workspace settings
workspace:billing      # Access billing information
workspace:viewer        # View event logs
workspace:branding     # Manage branding settings
workspace:integrations # Configure integrations

Analytics & Insights:

insights:view          # View analytics dashboard
insights:export        # Export analytics data
insights:advanced      # Access advanced analytics

Frontend Implementation

Permission Hook

The usePermissions hook provides React components with authorization state:

import { usePermissions } from '../hooks/usePermissions';

function ClusterDashboard() {
  const { 
    hasPermission, 
    hasRole, 
    canAccessPage,
    userPermissions,
    userRoles 
  } = usePermissions();

  // Check specific permission
  const canCreateCluster = hasPermission('cluster:create');
  
  // Check role
  const isAdmin = hasRole('workspace:admin');
  
  // Check page access
  const canViewBilling = canAccessPage('/billing');

  return (
    <div>
      {canCreateCluster && (
        <Button onClick={createCluster}>Create Cluster</Button>
      )}
      
      {isAdmin && (
        <AdminPanel />
      )}
    </div>
  );
}

Protected Components

Use the ProtectedRoute component to protect entire pages:

// Single permission requirement
<ProtectedRoute requiredPermissions={['cluster:view']}>
  <ClusterListPage />
</ProtectedRoute>

// Multiple permissions (all required)
<ProtectedRoute 
  requiredPermissions={['cluster:view', 'cluster:update']}
  requireAll={true}
>
  <ClusterEditPage />
</ProtectedRoute>

// Any of multiple permissions
<ProtectedRoute 
  requiredPermissions={['cluster:view', 'application:view']}
  requireAll={false}
>
  <DashboardPage />
</ProtectedRoute>

// Role-based protection
<ProtectedRoute requiredRoles={['workspace:admin']}>
  <WorkspaceSettingsPage />
</ProtectedRoute>

Conditional Rendering

For fine-grained UI control:

import { PermissionGuard } from '../components/PermissionGuard';

function Sidebar() {
  return (
    <nav>
      <PermissionGuard permission="cluster:view">
        <NavItem to="/clusters">Clusters</NavItem>
      </PermissionGuard>
      
      <PermissionGuard permission="application:view">
        <NavItem to="/applications">Applications</NavItem>
      </PermissionGuard>
      
      <PermissionGuard role="workspace:admin">
        <NavItem to="/settings">Settings</NavItem>
      </PermissionGuard>
    </nav>
  );
}

Backend Implementation

Permission Middleware

Protect API endpoints with the permission middleware:

// Setup authorization middleware
authzService := auth.NewAuthorizationService(keycloakClient)
permMiddleware := auth.NewPermissionMiddleware(authzService)

// Single permission protection
router.Handle("/api/clusters", 
  permMiddleware.RequirePermission("cluster:create")(createClusterHandler)).Methods("POST")

// Multiple permissions (all required)
router.Handle("/api/clusters/{id}", 
  permMiddleware.RequirePermissions([]string{
    "cluster:view", 
    "cluster:update",
  })(updateClusterHandler)).Methods("PUT")

// Role-based protection
router.Handle("/api/admin/users", 
  permMiddleware.RequireRole("workspace:admin")(adminUsersHandler)).Methods("GET")

// Custom authorization logic
router.Handle("/api/clusters/{id}/logs", 
  permMiddleware.RequireCustom(func(ctx *auth.AuthContext) bool {
    return ctx.HasPermission("cluster:logs") || 
           (ctx.HasPermission("cluster:view") && ctx.IsClusterOwner(clusterID))
  })(clusterLogsHandler)).Methods("GET")

Authorization Context

Access user authorization information in handlers:

func clusterHandler(w http.ResponseWriter, r *http.Request) {
  // Get authorization context
  authCtx := auth.GetAuthContext(r.Context())
  
  // Check permissions
  if authCtx.HasPermission("cluster:create") {
    // Allow cluster creation
  }
  
  // Check roles
  if authCtx.HasRole("workspace:admin") {
    // Admin-specific logic
  }
  
  // Get user permissions for filtering
  userPermissions := authCtx.GetPermissions()
  clusters := filterClustersByPermissions(allClusters, userPermissions)
  
  // Get user roles
  userRoles := authCtx.GetRoles()
  
  // Check resource ownership
  if authCtx.IsResourceOwner("cluster", clusterID) {
    // Owner-specific access
  }
}

Permission Inheritance

Hierarchical Permissions

Some permissions automatically inherit others:

workspace:admin → All workspace permissions
cluster:admin → cluster:view, cluster:update, cluster:logs
application:admin → application:view, application:update, application:test

Scoped Permissions

Permissions can be scoped to specific resources:

cluster:view:own         # View only owned clusters
user:manage:team         # Manage only team members
billing:view:workspace   # View billing for current workspace

Dynamic Authorization

Attribute-Based Access Control (ABAC)

For complex authorization scenarios:

// Time-based access
func isBusinessHours() bool {
  now := time.Now()
  return now.Hour() >= 9 && now.Hour() <= 17
}

// Location-based access
func isFromAllowedIP(ip string) bool {
  // Check against allowlist
  return ipAllowlist.Contains(ip)
}

// Resource-based access
func canAccessCluster(userID, clusterID string) bool {
  cluster := getCluster(clusterID)
  return cluster.OwnerID == userID || 
         cluster.SharedWith.Contains(userID)
}

Policy Evaluation

Custom policies for complex scenarios:

type Policy struct {
  Name        string
  Description string
  Rules       []Rule
}

type Rule struct {
  Condition string // "role", "permission", "attribute", "custom"
  Operator  string // "equals", "contains", "in", "not_in"
  Value     interface{}
  Logic     string // "AND", "OR", "NOT"
}

// Example: Business hours access policy
businessHoursPolicy := Policy{
  Name: "business_hours_access",
  Rules: []Rule{
    {
      Condition: "time",
      Operator:  "between",
      Value:     []string{"09:00", "17:00"},
      Logic:     "AND",
    },
    {
      Condition: "role",
      Operator:  "not_equals",
      Value:     "workspace:admin", // Admins can access anytime
      Logic:     "OR",
    },
  },
}

Administrative Features

Role Management Interface

Manage user roles and permissions through the Skycloak dashboard:

Assigning Roles:

  1. Navigate to SettingsWorkspaceMembers
  2. Find the user you want to modify
  3. Click the Edit button next to their name
  4. Select the appropriate role from the dropdown
  5. Save changes

Viewing Permissions:

  • User permissions are displayed in their profile
  • Role definitions show included permissions
  • Audit logs track all permission changes

Permission Auditing

Track permission changes and access:

// Log permission checks
func logPermissionCheck(userID, permission string, granted bool) {
  audit.Log(audit.Event{
    Type:      "permission_check",
    UserID:    userID,
    Resource:  permission,
    Action:    "check",
    Result:    granted,
    Timestamp: time.Now(),
  })
}

// Log role assignments
func logRoleAssignment(adminID, userID, role string) {
  audit.Log(audit.Event{
    Type:      "role_assignment",
    UserID:    adminID,
    TargetID:  userID,
    Resource:  role,
    Action:    "assign",
    Timestamp: time.Now(),
  })
}

Best Practices

Security Guidelines

Principle of Least Privilege:

  • Start with minimal permissions
  • Grant additional access as needed
  • Regularly review and audit permissions
  • Remove unused permissions promptly

Defense in Depth:

  • Implement authorization at multiple layers
  • UI-level restrictions for user experience
  • API-level enforcement for security
  • Database-level controls where applicable

Permission Granularity:

  • Use specific permissions over broad roles
  • Combine permissions logically
  • Avoid permission explosion
  • Document permission purposes

Performance Considerations

Caching Strategy:

  • Cache user permissions and roles
  • Implement cache invalidation on changes
  • Use Redis for distributed caching
  • Monitor cache hit rates

Optimization Techniques:

  • Batch permission checks when possible
  • Pre-calculate common permission combinations
  • Use lazy loading for complex policies
  • Monitor authorization performance

Troubleshooting

Common Issues

Permission Denied Errors:

  1. Verify user has required role
  2. Check permission inheritance
  3. Validate token claims
  4. Review policy configuration

Performance Problems:

  1. Check authorization cache hit rates
  2. Monitor permission check frequency
  3. Optimize policy evaluation
  4. Review database query performance

Inconsistent Authorization:

  1. Verify frontend/backend sync
  2. Check cache coherence
  3. Validate role propagation
  4. Review permission inheritance

Debugging Tools

Authorization Debug Mode:

// Enable debug mode in development
const { hasPermission, debugInfo } = usePermissions({ debug: true });

console.log('User permissions:', debugInfo.permissions);
console.log('User roles:', debugInfo.roles);
console.log('Permission check result:', debugInfo.lastCheck);

Backend Authorization Logging:

// Enable detailed authorization logging
auth.SetLogLevel(auth.LogLevelDebug)

// This will log:
// - Permission checks and results
// - Role evaluations
// - Policy executions
// - Cache hits/misses

Migration and Deployment

Role Migration

When updating the authorization model:

// Migration script example
func migrateRoles() error {
  // Map old roles to new roles
  roleMapping := map[string]string{
    "admin":     "workspace:admin",
    "viewer":    "cluster:viewer",
    "member":    "cluster:admin",
    "billing":   "workspace:billing",
  }
  
  for oldRole, newRole := range roleMapping {
    users := getUsersWithRole(oldRole)
    for _, user := range users {
      assignRole(user.ID, newRole)
      removeRole(user.ID, oldRole)
    }
  }
  
  return nil
}

Testing Authorization

// Test permission enforcement
func TestClusterCreatePermission(t *testing.T) {
  // Setup test user without permission
  user := createTestUser()
  
  // Attempt to create cluster
  response := makeAuthenticatedRequest("/api/clusters", user.Token)
  
  // Should be denied
  assert.Equal(t, http.StatusForbidden, response.StatusCode)
  
  // Grant permission
  assignPermission(user.ID, "cluster:create")
  
  // Retry request
  response = makeAuthenticatedRequest("/api/clusters", user.Token)
  
  // Should succeed
  assert.Equal(t, http.StatusCreated, response.StatusCode)
}

Next Steps