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:
- Navigate to Settings → Workspace → Members
- Find the user you want to modify
- Click the Edit button next to their name
- Select the appropriate role from the dropdown
- 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:
- Verify user has required role
- Check permission inheritance
- Validate token claims
- Review policy configuration
Performance Problems:
- Check authorization cache hit rates
- Monitor permission check frequency
- Optimize policy evaluation
- Review database query performance
Inconsistent Authorization:
- Verify frontend/backend sync
- Check cache coherence
- Validate role propagation
- 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/missesMigration 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)
}