public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Daniel Kral" <d.kral@proxmox.com>
To: "David Riley" <d.riley@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, 06 Jul 2026 13:56:15 +0200	[thread overview]
Message-ID: <DJRGWNICETBA.1ED2HNONXOCNG@proxmox.com> (raw)
In-Reply-To: <20260626131035.112374-6-d.riley@proxmox.com>

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.

>      # 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?

>                  } 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?

> +        }
>      }
>  
>      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...

> +        }
> +    }
> +
>      # 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",
>      },
>  ];






  parent reply	other threads:[~2026-07-06 11:56 UTC|newest]

Thread overview: 23+ 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-06-26 13:10 ` [PATCH pve-manager v2 03/10] fix #7294: ui: " David Riley
2026-07-06 14:17   ` Daniel Kral
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-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-06 11:56   ` Daniel Kral [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-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-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-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-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=DJRGWNICETBA.1ED2HNONXOCNG@proxmox.com \
    --to=d.kral@proxmox.com \
    --cc=d.riley@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