public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: David Riley <d.riley@proxmox.com>
To: Daniel Kral <d.kral@proxmox.com>, pve-devel@lists.proxmox.com
Subject: Re: [PATCH pve-access-control v2 05/10] fix #7294: acl: pool: add SDN VNets as pool members
Date: Mon, 20 Jul 2026 10:23:44 +0200	[thread overview]
Message-ID: <62179d8a-f522-4327-9dc3-600ccd01c71d@proxmox.com> (raw)
In-Reply-To: <DJRGWNICETBA.1ED2HNONXOCNG@proxmox.com>

comments inline.
I'll address the nits in v3.
Thanks.

On 7/6/26 1:56 PM, Daniel Kral wrote:
> Generally, it would be good if this patch also introduces the
> network_list entry in the repository's README, where the pool fields are
> documented for a quick overview.
>
>
> On Fri Jun 26, 2026 at 3:10 PM CEST, David Riley wrote:
>> diff --git a/src/PVE/RPCEnvironment.pm b/src/PVE/RPCEnvironment.pm
>> index 7591aa9..d9372a0 100644
>> --- a/src/PVE/RPCEnvironment.pm
>> +++ b/src/PVE/RPCEnvironment.pm
>> @@ -53,6 +53,21 @@ my $compile_acl_path = sub {
>>                       $data->{poolroles}->{"/storage/$storeid"}->{$role} = 1;
>>                   }
>>               }
>> +
>> +            for my $network_key (keys $d->{network}->%*) {
>> +                my ($type, @path) = split('/', $network_key);
>> +
>> +                if ($type eq 'vnet') {
>> +                    my ($zoneid, $vnetid, $vlan) = @path;
>> +
>> +                    my $acl_path = "/sdn/zones/$zoneid/$vnetid";
>> +                    $acl_path = "$acl_path/$vlan" if defined($vlan);
> nit: use the .= operator here
>
>> +
>> +                    for my $role (keys $pool_roles->%*) {
>> +                        $data->{poolroles}->{$acl_path}->{$role} = 1;
>> +                    }
>> +                }
>> +            }
>>           }
>>       }
>>   
>> @@ -63,15 +78,30 @@ my $compile_acl_path = sub {
>>       # means the role is set
>>       my $roles = PVE::AccessControl::roles($cfg, $user, $path);
>>   
>> +    my $poolroles_path = $data->{poolroles}->{$path};
> the factoring of $data->{poolroles}->{$path} should be done in a
> separate commit beforehand so the changes done in this commit are easier
> to understand and review.
>
>> +
>> +    # Pool ACL paths are setup without propagation, therefore checking
>> +    # /sdn/zones/<zone>/<vnet>/<tag> fails even if the user has the
>> +    # permission for the base path /sdn/zones/<zone>/<vnet>.
>> +    # To allow this, the roles of the base VNet path are looked up if
>> +    # the exact tagged path is not found in the pool.
>> +    if (!defined($poolroles_path) && $path =~ m|^/sdn/zones/[^/]+/[^/]+/\d+$|) {
>> +        my @parts = split('/', $path);
>> +        my $base_vnet_path = join('/', @parts[0 .. 4]); # remove tag
>> +
>> +        # Inherit the permissions of the base VNet path for this request
>> +        $poolroles_path = $data->{poolroles}->{$base_vnet_path};
>> +    }
>> +
> nit: it's probably better if this is moved before the declaration of
>       $roles so it's clear that it isn't depending on it/interacting with
>       it, but only the code below is
>
> Besides that, I'm curious whether there might be an use case where this
> might not be wanted? AFAIK, assigning a VM to a VNet does not include
> the network as assigning a VM to a VNet VLAN tag would have, so the
> propagation might be a bit off here...
>
> It would be possible to introduce the syntax
>
>      vnet/<zoneid>/<vnetid>/*
>
> to make the propagation to the individual vlan tag objects more
> explicit, so this can still be an opt-in feature, and otherwise
>
>      vnet/<zoneid>/<vnetid>
>
> only grants assigning the VNet to pool guests.
>
> Though this is only an idea.


Thanks for the suggestion. I think the using a '*' to make it
more explicit is a great idea.

I'll try it out and check if there are any downsides to this.

>>       # apply roles inherited from pools
>> -    if ($data->{poolroles}->{$path}) {
>> +    if ($poolroles_path) {
>>           # NoAccess must not be trumped by pool ACLs
>>           if (!defined($roles->{NoAccess})) {
>> -            if ($data->{poolroles}->{$path}->{NoAccess}) {
>> +            if ($poolroles_path->{NoAccess}) {
>>                   # but pool ACL NoAccess trumps regular ACL
>>                   $roles = { 'NoAccess' => 0 };
>>               } else {
>> -                foreach my $role (keys %{ $data->{poolroles}->{$path} }) {
>> +                for my $role (keys $poolroles_path->%*) {
>>                       # only use role from pool ACL if regular ACL didn't already
>>                       # set it, and never set propagation for pool-derived ACLs
>>                       $roles->{$role} = 0 if !defined($roles->{$role});
>> @@ -219,6 +249,8 @@ sub compute_api_permission {
>>                       $res->{vms}->{$priv} = 1;
>>                   } elsif ($priv =~ m/^Datastore\./) {
>>                       $res->{storage}->{$priv} = 1;
>> +                } elsif ($priv =~ m/^SDN\./) {
>> +                    $res->{sdn}->{$priv} = 1;
> What is the intent here to add this?
>
> I'm not too familiar with compute_api_permission(), but it seems like
> it's only relevant for OpenID and TFA authentication?


I think you are right this was a mistake on my side. I'll remove it completely.

>>                   } elsif ($priv eq 'Permissions.Modify') {
>>                       $res->{storage}->{$priv} = 1;
>>                       $res->{vms}->{$priv} = 1;
>> @@ -274,6 +306,17 @@ sub get_effective_permissions {
>>           foreach my $storeid (keys %{ $d->{storage} }) {
>>               $paths->{"/storage/$storeid"} = 1;
>>           }
>> +
>> +        for my $network_key (keys $d->{network}->%*) {
>> +            my ($type, @path) = split('/', $network_key);
>> +
>> +            if ($type eq 'vnet') {
>> +                my ($zoneid, $vnetid, $vlan) = @path;
>> +                my $vnet_path = "/sdn/zones/$zoneid/$vnetid";
>> +                $vnet_path .= "/$vlan" if defined($vlan);
>> +                $paths->{$vnet_path} = 1;
>> +            }
> This might benefit from a warn/log_warn if there's a malformed network
> pool entry or one with an unknown type... Though this might not get
> shown properly to users as this is only called from an API endpoint?


I agree, that would actually improve debugging faulty configurations.
I'll add a log statement here.


>> +        }
>>       }
>>   
>>       my $perms = {};
>> @@ -353,6 +396,25 @@ sub check_sdn_bridge {
>>           }
>>       }
>>   
>> +    # check access to VLANs via pools
>> +    for my $pool (keys $cfg->{pools}->%*) {
>> +        my $poolcfg = $cfg->{pools}->{$pool};
>> +        next if !$poolcfg->{network};
>> +
>> +        for my $network_key (keys $poolcfg->{network}->%*) {
>> +            my ($type, @path) = split('/', $network_key);
>> +
>> +            if (defined($type) && $type eq 'vnet') {
>> +                my ($zoneid, $vnetid, $vlan) = @path;
>> +
>> +                if ($zoneid eq $zone && $vnetid eq $bridge && defined($vlan)) {
>> +                    my $vlanpath = "/sdn/zones/$zoneid/$vnetid/$vlan";
>> +                    return 1 if $self->check_any($username, $vlanpath, $privs, 1);
>> +                }
>> +            }
> I think this should also have an else path, where we warn/log_warn that
> there's a malformed network pool entry or one with an unknown type...


Will add that for visibility.

>> +        }
>> +    }
>> +
>>       # repeat check, but fatal
>>       $self->check_any($username, $path, $privs, 0) if !$noerr;
>>   
>> diff --git a/src/test/parser_writer.pl b/src/test/parser_writer.pl
>> index ea2778e..a809e21 100755
>> --- a/src/test/parser_writer.pl
>> +++ b/src/test/parser_writer.pl
>> @@ -238,24 +238,35 @@ my $default_cfg = {
>>           vms => {},
>>           storage => {},
>>           pools => {},
>> +        network => {},
>>       },
>>       test_pool_members => {
>>           'id' => 'testpool',
>>           vms => { 123 => 1, 1234 => 1 },
>>           storage => { 'local' => 1, 'local-zfs' => 1 },
>>           pools => {},
>> +        network => { "vnet/zone1/vnet1" => 1, 'vnet/zone2/vnet2' => 1 },
>>       },
>>       test_pool_duplicate_vms => {
>>           'id' => 'test_duplicate_vms',
>>           vms => {},
>>           storage => {},
>>           pools => {},
>> +        network => {},
>>       },
>>       test_pool_duplicate_storages => {
>>           'id' => 'test_duplicate_storages',
>>           vms => {},
>>           storage => { 'local' => 1, 'local-zfs' => 1 },
>>           pools => {},
>> +        network => {},
>> +    },
>> +    test_pool_duplicate_networks => {
>> +        'id' => 'test_duplicate_networks',
>> +        vms => {},
>> +        storage => {},
>> +        pools => {},
>> +        network => { "vnet/zone1/vnet1" => 1, "vnet/zone2/vnet2" => 1 },
>>       },
>>       acl_simple_user => {
>>           'path' => '/',
>> @@ -431,12 +442,15 @@ my $default_raw = {
>>           'test_role_privs_invalid' => 'role:testrole:VM.Invalid,Datastore.Audit,VM.Allocate:',
>>       },
>>       pools => {
>> -        'test_pool_empty' => 'pool:testpool::::',
>> -        'test_pool_invalid' => 'pool:testpool::non-numeric:inval!d:',
>> -        'test_pool_members' => 'pool:testpool::123,1234:local,local-zfs:',
>> -        'test_pool_duplicate_vms' => 'pool:test_duplicate_vms::123,1234::',
>> -        'test_pool_duplicate_vms_expected' => 'pool:test_duplicate_vms::::',
>> -        'test_pool_duplicate_storages' => 'pool:test_duplicate_storages:::local,local-zfs:',
>> +        'test_pool_empty' => 'pool:testpool:::::',
>> +        'test_pool_invalid' => 'pool:testpool::non-numeric:inval!d::',
>> +        'test_pool_members' =>
>> +            'pool:testpool::123,1234:local,local-zfs:vnet/zone1/vnet1,vnet/zone2/vnet2:',
>> +        'test_pool_duplicate_vms' => 'pool:test_duplicate_vms::123,1234:::',
>> +        'test_pool_duplicate_vms_expected' => 'pool:test_duplicate_vms:::::',
>> +        'test_pool_duplicate_storages' => 'pool:test_duplicate_storages:::local,local-zfs::',
>> +        'test_pool_duplicate_networks' =>
>> +            'pool:test_duplicate_networks::::vnet/zone1/vnet1,vnet/zone2/vnet2:',
>>       },
>>       acl => {
>>           'acl_simple_user' => 'acl:1:/:test@pam:PVEVMAdmin:',
>> @@ -696,6 +710,7 @@ my $tests = [
>>                       $default_cfg->{test_pool_members},
>>                       $default_cfg->{test_pool_duplicate_vms},
>>                       $default_cfg->{test_pool_duplicate_storages},
>> +                    $default_cfg->{test_pool_duplicate_networks},
>>                   ],
>>               ),
>>               vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
>> @@ -705,15 +720,37 @@ my $tests = [
>>               . "\n\n\n"
>>               . $default_raw->{pools}->{'test_pool_members'} . "\n"
>>               . $default_raw->{pools}->{'test_pool_duplicate_vms'} . "\n"
>> -            . $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n",
>> +            . $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n"
>> +            . $default_raw->{pools}->{'test_pool_duplicate_networks'} . "\n",
>>           expected_raw => ""
>>               . $default_raw->{users}->{'root@pam'}
>>               . "\n\n\n"
>> +            . $default_raw->{pools}->{'test_pool_duplicate_networks'} . "\n"
>>               . $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n"
>>               . $default_raw->{pools}->{'test_pool_duplicate_vms_expected'} . "\n"
>>               . $default_raw->{pools}->{'test_pool_members'}
>>               . "\n\n\n",
>>       },
>> +    {
>> +        name => "pool_format_backward_compatibility",
>> +        config => {
>> +            acl_root => default_acls(),
>> +            users => default_users(),
>> +            roles => default_roles(),
>> +            pools => default_pools_with([$default_cfg->{test_pool_empty}]),
>> +        },
>> +        raw => ""
>> +            . $default_raw->{users}->{'root@pam'}
>> +            . "\n\n\n"
>> +            # old format: 4 colons
>> +            . "pool:testpool::::\n\n\n",
>> +        expected_raw => ""
>> +            . $default_raw->{users}->{'root@pam'}
>> +            . "\n\n\n"
>> +            # new format 5: colons
>> +            . $default_raw->{pools}->{'test_pool_empty'}
>> +            . "\n\n\n",
>> +    },
> nice!
>
>>       {
>>           name => "acl_simple_user",
>>           config => {
>> @@ -1102,7 +1139,7 @@ my $tests = [
>>               . 'user:test@pam:0:0::::::' . "\n"
>>               . 'token:test@pam!test:0:0::' . "\n\n"
>>               . 'group:testgroup:::' . "\n\n"
>> -            . 'pool:testpool::::' . "\n\n"
>> +            . 'pool:testpool:::::' . "\n\n"
>>               . 'role:testrole::' . "\n\n",
>>       },
>>   ];
>




  reply	other threads:[~2026-07-20  8:24 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-26 13:10 [PATCH access-control/cluster/common/manager/network/proxmox-widget-toolkit/qemu-server v2 00/10] fix #7294: pool: add SDN VNets as pool members David Riley
2026-06-26 13:10 ` [PATCH pve-manager v2 01/10] ui: replace var with let to match style guide for variable declaration David Riley
2026-07-03 13:14   ` Daniel Kral
2026-06-26 13:10 ` [PATCH pve-manager v2 02/10] fix #7294: api: pool: add SDN VNets as pool members David Riley
2026-07-03 13:19   ` Daniel Kral
2026-07-20  8:20     ` David Riley
2026-06-26 13:10 ` [PATCH pve-manager v2 03/10] fix #7294: ui: " David Riley
2026-07-06 14:17   ` Daniel Kral
2026-07-20  8:21     ` David Riley
2026-06-26 13:10 ` [PATCH proxmox-widget-toolkit v2 04/10] fix #7294: css: theme: add opacity override for pool VNet icon David Riley
2026-07-03 13:30   ` Daniel Kral
2026-07-20  8:22     ` David Riley
2026-06-26 13:10 ` [PATCH pve-access-control v2 05/10] fix #7294: acl: pool: add SDN VNets as pool members David Riley
2026-07-03 14:35   ` Daniel Kral
2026-07-20  8:24     ` David Riley
2026-07-06 11:56   ` Daniel Kral
2026-07-20  8:23     ` David Riley [this message]
2026-06-26 13:10 ` [PATCH pve-network v2 06/10] fix #7294: sdn: register api formats for zones and vnets David Riley
2026-07-06 12:21   ` Daniel Kral
2026-07-20  8:24     ` David Riley
2026-06-26 13:10 ` [PATCH pve-network v2 07/10] fix #7294: sdn: vnet: update pool members on vnet migration and deletion David Riley
2026-07-06 12:29   ` Daniel Kral
2026-07-20  8:24     ` David Riley
2026-06-26 13:10 ` [PATCH pve-common v2 08/10] tools: add helpers for version comparison David Riley
2026-07-06 13:02   ` Daniel Kral
2026-06-26 13:10 ` [PATCH pve-cluster v2 09/10] fix #7294: cluster: helpers: add cluster-wide version assertion David Riley
2026-07-06 13:00   ` Daniel Kral
2026-07-20  8:25     ` David Riley
2026-06-26 13:10 ` [PATCH qemu-server v2 10/10] fix #7294: helpers: use cluster-wide version helper David Riley
2026-07-06 13:20   ` Daniel Kral
2026-07-20  8:26     ` David Riley
2026-07-06 14:07 ` [PATCH access-control/cluster/common/manager/network/proxmox-widget-toolkit/qemu-server v2 00/10] fix #7294: pool: add SDN VNets as pool members Daniel Kral

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=62179d8a-f522-4327-9dc3-600ccd01c71d@proxmox.com \
    --to=d.riley@proxmox.com \
    --cc=d.kral@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal