<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ACL V2, we need your feedbacks!]]></title><description><![CDATA[<h1>ACL v2: Fine-grained access control in Xen Orchestra</h1>
<p dir="auto">With the v2 of the ACL system, Xen Orchestra takes a new step forward in permission management. Where v1 offered basic per-object access control, v2 introduces a full <strong>RBAC (Role-Based Access Control)</strong> model, with effects, selectors, and an action hierarchy.</p>
<h2>What changes</h2>
<p dir="auto">The old approach allowed granting access to an object (a VM, an SR…). Simple, but limited: there was no way to say <em>"this user can shutdown only VM with tag: foo"</em>.</p>
<p dir="auto">Another major limitation of v1: it only covered <strong>XAPI objects</strong> — VMs, hosts, SRs, networks, so user, groups, backups, schedules, jobs,... was out of scope.</p>
<h2>ACL v2: REST API exclusive</h2>
<p dir="auto">ACL v2 is <strong>available through the REST API only</strong>. The JSON-RPC API (used by XO5) stays on ACL v1, and conversely: ACL v1 is not available on the REST API.</p>
<h2>Key concepts</h2>
<h3>Roles and privileges</h3>
<p dir="auto">A <strong>role</strong> is a named set of <strong>privileges</strong>. Each privilege defines:</p>
<ul>
<li>a <strong>resource</strong> type (<code>vm</code>, <code>sr</code>, <code>network</code>, <code>backup-job</code>…)</li>
<li>an <strong>action</strong> (<code>read</code>, <code>start</code>, <code>shutdown:clean</code>, <code>delete</code>…)</li>
<li>an <strong>effect</strong>: <code>allow</code> or <code>deny</code></li>
<li>an optional <strong>selector</strong> to target specific objects (complex-matcher format)</li>
</ul>
<p dir="auto">Actions are hierarchical. Granting <code>shutdown</code> covers both <code>shutdown:clean</code> and <code>shutdown:hard</code>. But granting <code>shutdown:clean</code> does not cover <code>shutdown</code> as a whole. <code>deny</code> always takes precedence over <code>allow</code>.</p>
<h3>Built-in roles</h3>
<p dir="auto">Actually, 4 template roles are provided out of the box:</p>
<ul>
<li><strong>Read only</strong> — full read-only access to the infrastructure</li>
<li><strong>VMs read only</strong> — read-only access to VMs only</li>
<li><strong>VMs power state manager</strong> — manage VM power state (start, stop, reboot, pause…)</li>
<li><strong>VMs creator</strong> — create VMs from templates</li>
</ul>
<p dir="auto">These roles are immutable and automatically updated on startup — they cannot be assigned directly. To use them, copy the template into a new role and assign that copy to your users or groups. This ensures the built-in templates always stay up to date without affecting your custom configurations.</p>
<h3>Selectors: object-level precision</h3>
<p dir="auto">A selector restricts a privilege to objects matching certain properties. For example:<code>tags:qa</code><br />
This allows add a privilege <em>only on VMs tagged <code>qa</code></em>.</p>
<p dir="auto">What makes this mechanism powerful is its <strong>dynamic</strong> nature. Selectors are evaluated in real time.<br />
In case the users subscribed to VMs changes, if the <code>qa</code> tag is added to an existing VM, and the user have a <code>read</code> privilege, he will see that VM appear as a new object — the user will receive an <code>add</code> event, not an <code>update</code>. Conversely, if the tag is removed, he will receive a <code>remove</code> event: the VM disappears from his scope.</p>
<p dir="auto">Events are always <strong>from the user's perspective</strong>, not XOA's. For XOA, it is a simple tag update. For the ACL user, it is an object entering or leaving their scope.</p>
<p dir="auto">This enables very practical use cases: a single tag is enough to grant or revoke access to a resource, without touching roles or privileges at all.</p>
<h3>Assigning Roles to Users and Groups</h3>
<p dir="auto">A role can be attached to a user or a group. A user's effective roles are the union of their direct roles and those of their groups.</p>
<h2>REST API integration</h2>
<p dir="auto">All endpoints are exposed through the REST API:</p>
<ul>
<li><code>GET/POST /acl-roles</code> — list and create roles</li>
<li><code>PUT/DELETE /acl-roles/{id}/users/{userId}</code> — attach/detach a role to a user</li>
<li><code>PUT/DELETE /acl-roles/{id}/groups/{groupId}</code> — attach/detach a role to a group</li>
<li><code>GET/POST /acl-privileges</code> — list and create role's privileges</li>
<li><code>POST /acl-roles/{id}/actions/copy</code> — copy a role</li>
</ul>
<p dir="auto">Each REST API endpoint declares the <strong>required privileges</strong> to access it via the swagger UI. If an endpoint declares none, it is <strong>admin-only</strong>.</p>
<h2>A concrete example</h2>
<blockquote>
<p dir="auto">Alice is a member of the QA team. She needs to be able to start and stop VMs in her test environment, but must not touch anything in production.</p>
</blockquote>
<p dir="auto">With ACL v2:</p>
<ol>
<li>Create a <code>QA Operator</code> role with following privileges:
<ul>
<li><code>{resource: 'vm', action: 'read', effect: 'allow', selector: 'tags:qa'}</code></li>
<li><code>{resource: 'vm', action: 'start', effect: 'allow', selector: 'tags:qa'}</code></li>
<li><code>{resource: 'vm', action: 'stop', effect: 'allow', selector: 'tags:qa'}</code></li>
</ul>
</li>
<li>Attach this role to Alice (or her group)</li>
</ol>
<p dir="auto">That's it. Alice cannot touch production VMs, and any attempt is blocked with an explicit error.</p>
<h2>Another concrete example</h2>
<blockquote>
<p dir="auto">Bob is allowed to rename VMs, but only while they are running — to prevent renaming VMs that are off and might be part of an automated process.</p>
</blockquote>
<p dir="auto">With ACL v2:</p>
<ol>
<li>Create a <code>Running VM Renamer</code> role with following privilege:
<ul>
<li><code>{resource: 'vm', action: 'read', effect: 'allow', selector: 'power_state:Running'</code></li>
<li><code>{resource: 'vm', action: 'update:name_label', effect: 'allow', selector: 'power_state:Running'</code></li>
</ul>
</li>
<li>Attach this role to Bob</li>
</ol>
<p dir="auto">Bob can rename and see any running VM.</p>
<h3>One last example</h3>
<blockquote>
<p dir="auto">Carol can see all VMs in the infrastructure, except those tagged <code>prod</code>.</p>
</blockquote>
<p dir="auto">With ACL v2:</p>
<ol>
<li>Create a <code>Non-Prod VM Reader</code> role with two privileges:
<ul>
<li><code>{resource: 'vm', action: 'read', effect: 'allow'}</code> no selector, grants read access to all VMs</li>
<li><code>{resource: 'vm', action: 'read', effect: 'deny', selector: 'tags:prod'</code> explicitly denies access to production VMs</li>
</ul>
</li>
<li>Attach this role to Carol</li>
</ol>
<p dir="auto">Since <code>deny</code> always takes precedence over <code>allow</code>, Carol can browse the full VM list — except production VMs, which are completely invisible to her.</p>
<p dir="auto"><a href="https://github.com/vatesfr/xen-orchestra/tree/mra-acl-v2/%40xen-orchestra/acl/src/actions" target="_blank" rel="noopener noreferrer nofollow ugc">List of possible actions (by resource)</a></p>
<pre><code class="language-json">{
  update: {
    name_label:true,
    name_description:true,
    ...
  }
} 
</code></pre>
<p dir="auto">is translated into -&gt; <code>update:name_label</code>, <code>update:name_description</code>, ...</p>
<p dir="auto">Please note that ACL v2 is currently only accessible via the REST API. Support in the XO6 user interface will be available later.</p>
]]></description><link>https://xcp-ng.org/forum/topic/12036/acl-v2-we-need-your-feedbacks</link><generator>RSS for Node</generator><lastBuildDate>Thu, 17 Sep 2026 04:08:12 GMT</lastBuildDate><atom:link href="https://xcp-ng.org/forum/topic/12036.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 30 Mar 2026 12:03:55 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to ACL V2, we need your feedbacks! on Fri, 21 Aug 2026 20:33:33 GMT]]></title><description><![CDATA[<p dir="auto">I'm late to this, but I've been building lately a JetBrains plugin against the REST API and ACL v2 turned out to decide its whole design, so here's some feedback.<br />
Everything below is just one appliance, one pool (my small homelab), on a plan 4 trial, with an admin control call taken in the same breath as every scoped one. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f937.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--shrug" style="height:23px;width:auto;vertical-align:middle" title=":shrug:" alt="🤷" /></p>
<p dir="auto">What I experienced is that selectors narrow reads (<code>tags:</code> and <code>id:</code> both, 1 VM against an admin control of 11), they're re-evaluated per request rather than fixed when the privilege is created, <code>deny</code> composes the way your Carol example says (allow-all plus deny on a tag gave 9, which is 11 minus the 2 tagged), and they scope the power verbs too, not just reads.</p>
<p dir="auto">The event stream is, to me, scoped as well, which was the thing I most wanted to check, because a scoped read next to an unscoped feed would have been a nasty trap. It's not. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f389.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--tada" style="height:23px;width:auto;vertical-align:middle" title=":tada:" alt="🎉" /><br />
With two streams open at once, a change to an out-of-scope VM produced an <code>update</code> on the admin stream and nothing at all on the scoped one, so it's genuinely filtered rather than merely quiet.<br />
And the bit I'd underline: the same single change is <code>update</code> to the admin and <code>add</code> or <code>remove</code> to the scoped user. The verb is computed per subscriber, not per object. Across that run the admin only ever saw <code>update</code>, and I originally wrote that an admin never sees <code>add</code> or <code>remove</code> at all. That was wrong and I've since measured it: an admin does get <code>add</code> and <code>remove</code> when an object is genuinely created or deleted. So the rule is that a <strong>scope</strong> change reads as <code>update</code> and an <strong>existence</strong> change as <code>add</code> or <code>remove</code>, which supports your design better than what I first wrote did. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f44d.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--+1" style="height:23px;width:auto;vertical-align:middle" title=":+1:" alt="👍" /><br />
"From the user's perspective, not XOA's" turns out to be literal rather than a figure of speech, and that's a nicer piece of design than the sentence let me imagine.</p>
<p dir="auto">Now the three things that I didn't see in the post above.</p>
<p dir="auto"><code>vm-snapshot</code> is a separate privilege resource and <code>vm</code> doesn't imply it. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f937.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--shrug" style="height:23px;width:auto;vertical-align:middle" title=":shrug:" alt="🤷" /> Maybe that's obvious, but it was not obvious to me.<br />
With all six VM privileges granted, <code>GET /vm-snapshots</code> came back empty while admin saw 6, including a snapshot the scoped user had just taken. Anything with a restore or revert screen gets an empty list and no error.</p>
<p dir="auto">Inherited snapshot tags look like a snapshot-time copy rather than a link. Tag a VM and it's in scope immediately, but its existing snapshots keep <code>tags: []</code> and stay invisible. So someone onboarded into a tag scope after their snapshots exist sees the VM and not its history.</p>
<p dir="auto">Subscribing delivers no initial dump. Both streams sat on <code>init</code> and keepalives until something changed, so it's a delta feed and a client has to fetch the collection over REST and maintain it from events. Worth a line, since the natural assumption (at least to me, don't make that the rule for everyone) is the other one.</p>
<p dir="auto">While I'm here: two smaller ones. Privilege action names aren't REST action names: <code>shutdown:clean</code> grants <code>clean_shutdown</code>, <code>revert-snapshot</code> grants <code>revert_snapshot</code>, and a mistyped action quietly gives you a privilege that grants nothing. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f914.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--thinking_face" style="height:23px;width:auto;vertical-align:middle" title=":thinking_face:" alt="🤔" /><br />
And the <code>event: init</code> frame's field is <code>id</code>, not <code>connectionId</code> (why did I think it was <code>connectionId</code>, no idea, I thought it was "natural"), which cost me a while of thinking the stream was dead when I was posting to <code>/events//subscriptions</code>. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f926.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--face_palm" style="height:23px;width:auto;vertical-align:middle" title=":face_palm:" alt="🤦" /><br />
Yes, I know, I should have read the documentation instead of experimenting in the dark, sending made-up field names in the wild. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f937.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--shrug" style="height:23px;width:auto;vertical-align:middle" title=":shrug:" alt="🤷" /></p>
<p dir="auto">One last thing: <code>selector</code> is optional, so a privilege created without one reads back as <code>{id, resource, action, effect, roleId}</code> with no hint the field exists.<br />
I granted <code>allow read on vm</code>, saw all 11 VMs, read the object back, and (falsely) concluded the REST API had no object dimension at all.<br />
It's all in the previous post and it's in the swagger, and of course, in <a href="https://docs.xen-orchestra.com/rbac" target="_blank" rel="noopener noreferrer nofollow ugc">the official documentation</a>. Once again, I'm an innocent victim because I didn't RTFM. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f60a.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--blush" style="height:23px;width:auto;vertical-align:middle" title=":blush:" alt="😊" /><br />
I just never saw a privilege that had one. If a privilege echoed <code>selector: null</code>, or if the first example anyone met were a scoped one, I don't think I would have spent much time on that. Once again, my bad, didn't RTFM.</p>
<p dir="auto">This was a small, targeted test, at best. I didn't have the intent to test what was brought up in this very thread, I just happened to tinkle with the REST API and ACL V2 for my PoC, so lots of things got untested.<br />
Please, don't take it from me as settled: only <code>tags:</code> and <code>id:</code> selector forms, nothing on a second pool or a real multi-user deployment, and I have not checked what happens to a live subscription when the privilege itself changes rather than the VM's tags.</p>
<p dir="auto">If you read me until there, you're brave, or have too much time on your hands. <img src="https://xcp-ng.org/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f609.png?v=2bdbead4301" class="not-responsive emoji emoji-android emoji--wink" style="height:23px;width:auto;vertical-align:middle" title=":wink:" alt="😉" /></p>
]]></description><link>https://xcp-ng.org/forum/post/108007</link><guid isPermaLink="true">https://xcp-ng.org/forum/post/108007</guid><dc:creator><![CDATA[poddingue]]></dc:creator><pubDate>Fri, 21 Aug 2026 20:33:33 GMT</pubDate></item></channel></rss>