Skip to content

1. Prepare Proxmox

Everything the platform does on the hypervisor goes through one scoped API token over the REST API. No SSH, no qm, no writing under /etc/pve. Getting the privileges right here prevents a long tail of confusing failures later.

PrincipalPurpose
zone9-ro@pve!auditRead-only. Inspection, operator tooling, anything an assistant is pointed at
zone9-rw@pve!ctlThe platform’s write token. Narrow by design
Your own operator accountEverything the platform is not allowed to do

Keeping the read-only principal separate is worth the two extra commands: it means diagnostic tooling can never make a change.

Run on any cluster node. One line — a role definition broken across lines silently produces an empty privilege list, and the failures that follow do not point back here.

Terminal window
pveum role add PanelCtl -privs "VM.Allocate VM.Clone VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Cloudinit VM.Config.Options VM.Config.CDROM VM.Config.HWType VM.PowerMgmt VM.Audit VM.Console VM.GuestAgent.Audit Datastore.AllocateSpace Datastore.Audit SDN.Audit SDN.Use Pool.Allocate Pool.Audit Sys.Audit"
pveum user add zone9-rw@pve --comment "zone9 control plane"
pveum aclmod / --user zone9-rw@pve --role PanelCtl
pveum user token add zone9-rw@pve ctl --privsep 1

The last command prints the secret once.

  • VM.GuestAgent.Audit — the verification stage asks the guest agent for its network interfaces. Without it a server never gets an observed address.
  • SDN.Use — attaching a network interface to a VNet.
  • Pool.Allocate — per-organisation pools, so ownership is visible in the Proxmox UI.
  • VM.Console — the panel’s console relay.

Sys.Modify is not in the list, and that is the point of the whole design.

Customer firewalls were originally implemented as cluster security groups, which need system-modification rights. Rather than widen the token, the firewall was redesigned to write rules directly onto each virtual machine — keeping the token inside “manage virtual machines, do not change the system”. The user-visible model did not change.

SDN.Allocate is likewise absent: the SDN fabric is created once by an operator, not by the platform.

If you add a feature that seems to need a wider privilege, treat that as a signal to redesign the feature. That is the rule the codebase follows.

The ISO role — needed for managed Kubernetes

Section titled “The ISO role — needed for managed Kubernetes”

Talos machine configuration is delivered as a cidata ISO uploaded to a storage and attached to the VM. That needs privileges on one storage only:

Terminal window
pveum role add PanelISO --privs Datastore.AllocateTemplate,Datastore.Allocate,Datastore.Audit
pveum acl modify /storage/<iso-storage> --tokens 'zone9-rw@pve!ctl' --roles PanelISO
pveum acl modify /storage/<iso-storage> --users 'zone9-rw@pve' --roles PanelISO

Three things here trip people up:

Both ACL lines are mandatory. With privilege separation on, the effective permission is the intersection of the user’s permission and the token’s ACL. Granting only the token leaves the intersection empty, and the endpoint still returns 403 — with an error naming the privilege, which makes it look like the grant did not apply.

Datastore.Allocate is for deletion, not upload. The cidata ISO contains certificates and is removed once the node has booted. Without this privilege every cluster leaves one behind.

The grant is scoped to one storage on purpose. Attaching these privileges at / would propagate them to every storage in the cluster.

Terminal window
pveum role add PanelAudit -privs "VM.Audit Datastore.Audit SDN.Audit Sys.Audit Pool.Audit"
pveum user add zone9-ro@pve --comment "zone9 inspection"
pveum aclmod / --user zone9-ro@pve --role PanelAudit
pveum user token add zone9-ro@pve audit --privsep 1
Terminal window
pvesh get /access/roles/PanelCtl

Confirm the privilege list is populated and that Sys.Modify is not in it. An empty list here is the single most common cause of unexplained 403s later.

Then check the token works from wherever the agent will run:

Terminal window
curl -sk -H "Authorization: PVEAPIToken=zone9-rw@pve!ctl=<secret>" \
https://<node>:8006/api2/json/version

zone9 places disks on the storage you name in the region configuration. Ceph and ZFS with replication both work. The platform does not manage the backend, its encryption or its replication policy — configure those the way you would without zone9.