Concepts

Roles and Permissions

A five-role RBAC model enforced through OIDC token claims from Zitadel. Tenant-wide and VDC-scoped roles control access to VMs, networks, clusters, and platform administration.

flex.plane uses a five-role RBAC (Role-Based Access Control) model. This replaces what you'd traditionally configure with LDAP groups, Active Directory roles, or Keystone policies in OpenStack. Roles are managed in Zitadel and enforced by the orchestrator on every GraphQL operation.

Understand the role hierarchy

The role model has two scopes: tenant-wide roles that apply globally, and VDC-scoped roles that apply within a specific Virtual Datacenter.

Tenant-wide
ADMIN
  • Create and delete VDCs
  • Register hosts, update agents
  • Full platform administration
USER
  • Create and manage VMs
  • Manage profiles, images, storage
  • Provision Kubernetes clusters
Per VDC
VDC_ADMIN
  • Manage VDC members and roles
  • Configure gateway firewall
  • Full control within their VDC
VDC_USER
  • Create VMs and networks
  • Manage load balancers
  • Operate workloads within their VDC
ANONYMOUSPublic endpoints only, no authentication required

Roles are not hierarchical across scopes. An ADMIN does not automatically have VDC_ADMIN access to every VDC. The tenant-wide and VDC-scoped role systems are independent. A user can be an ADMIN at the tenant level and a VDC_USER in a specific VDC, or vice versa.

See how roles are assigned

Roles are stored in Zitadel as project role grants. There are two types:

Tenant-wide roles

Assigned directly to a user on the Zitadel project. These grant USER or ADMIN access across the entire tenant.

VDC-scoped roles

Assigned via VDC membership through the flex.plane portal or API (which creates the corresponding Zitadel role grant). These grant VDC_USER or VDC_ADMIN access within a specific VDC. In the portal, manage VDC members from the Members page.

The Members page lists all users in the VDC with their assigned role.

When a user authenticates, their OIDC token contains all their role grants. The orchestrator extracts these claims and builds the user's effective permissions for the current request.

How authorization is enforced

Every field in the GraphQL schema is annotated with a @hasRole directive that specifies which roles can access it:

extend type Query {
  vms: [VM!] @hasRole(role: [VDC_USER])
  hosts: [Host!] @hasRole(role: [USER])
  virtualDatacenters: [VirtualDatacenter!]! @hasRole(role: [ANONYMOUS])
}

extend type Mutation {
  createVM(...): VM! @hasRole(role: [USER])
  createVirtualDatacenter(...): VirtualDatacenter! @hasRole(role: [ADMIN])
  addMember(...): Member! @hasRole(role: [VDC_ADMIN])
}

The orchestrator checks the directive against the user's roles before executing any resolver. If the user does not have a matching role, the request is rejected with an authorization error.

Check what each role can do

The tables below map every GraphQL operation to the minimum required role. A "yes" means the role has access.

Queries

OperationANONYMOUSVDC_USERVDC_ADMINUSERADMIN
tenantyesyesyesyesyes
contextyesyesyesyesyes
virtualDatacentersyesyesyesyesyes
virtualDatacenter(id)--yesyesyesyes
vms / vm(vmId)--yesyesyesyes
vmBackups / vmSnapshots--yesyesyesyes
vmMetrics--yesyesyesyes
vmFirewallRules / vmFirewallOptions--yesyesyesyes
vmFiles / vmFileContent------yesyes
networks--yesyesyesyes
loadBalancers--yesyesyesyes
edgeGateway (VDC-scoped)--yesyesyesyes
edgeGateways (tenant-wide)------yesyes
zones / zone(zoneId)--yesyesyesyes
hosts / host(hostId)------yesyes
statistics--yesyesyesyes
computeProfiles--yesyesyesyes
storageProfiles--yesyesyesyes
vmImages--yesyesyesyes
defaultStorage / *StoragePools------yesyes
availableKubernetesVersions--yesyesyesyes
kubernetesClusters / kubernetesCluster--yesyesyesyes
kubernetesClusterKubeconfig--yesyesyesyes
members / users----yes--yes
auditLog--yesyesyesyes

Mutations: VM operations

OperationANONYMOUSVDC_USERVDC_ADMINUSERADMIN
createVM------yesyes
startVM / stopVM / rebootVM------yesyes
deleteVM------yesyes
scaleVM------yesyes
cloneVM------yesyes
remoteMigrateVM------yesyes
connectVMNetwork / disconnectVMNetwork------yesyes
attachVMDisk / detachVMDisk / resizeVMDisk------yesyes
backupVM / restoreVM / deleteVMBackup------yesyes
snapshotVM / rollbackVM / deleteVMSnapshot------yesyes
createVMFirewallRule / deleteVMFirewallRule------yesyes
setVMFirewallOptions------yesyes
writeVMFile------yesyes

Mutations: Networking and Kubernetes

OperationANONYMOUSVDC_USERVDC_ADMINUSERADMIN
createNetwork / deleteNetwork--yesyesyesyes
createLoadBalancer / deleteLoadBalancer--yesyesyesyes
setDefaultPolicy----yes--yes
addFirewallRule / deleteFirewallRule (gateway)----yes--yes
createKubernetesCluster------yesyes
scaleKubernetesCluster------yesyes
deleteKubernetesCluster------yesyes

Mutations: Administration

OperationANONYMOUSVDC_USERVDC_ADMINUSERADMIN
createVirtualDatacenter / deleteVirtualDatacenter--------yes
addMember / updateMemberRole / removeMember----yes--yes
addComputeProfile / updateComputeProfile / removeComputeProfile------yesyes
addStorageProfile / removeStorageProfile------yesyes
addVMImage / updateVMImage / removeVMImage------yesyes
setDefaultStorage------yesyes
registerHost--------yes
updateAgent--------yes
The USER and VDC_USER scopes are intentionally separate. A USER operates in tenant-wide context and manages platform resources (profiles, images, hosts). A VDC_USER operates within a specific VDC and manages workloads (VMs, networks). A person can hold both roles simultaneously.
For most multi-tenant setups, your platform engineers will have ADMIN + USER roles, while your customers or end users will have VDC_USER or VDC_ADMIN roles scoped to their respective VDCs.