From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id E27071FF0DF for ; Thu, 05 Nov 2026 12:20:16 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id B6D1F21B19; Thu, 05 Nov 2026 12:15:52 +0100 (CET) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server v4 42/45] network: update the microseg enforcement on a tag or name change Date: Thu, 5 Nov 2026 12:13:21 +0100 Message-ID: <20261105111324.706922-43-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261105111324.706922-1-h.laimer@proxmox.com> References: <20261105111324.706922-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793877253078 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.289 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: WR7YFHH4LGXDTVUP53HQ7TS2G62YP5BW X-Message-ID-Hash: WR7YFHH4LGXDTVUP53HQ7TS2G62YP5BW X-MailFrom: h.laimer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The groups of a VM's NICs depend on its tags and name, but a change of those took effect at the next start of the VM or the next network reload of the node. Update the NICs of a running VM once the new config is written, so the change applies right away. The update reads the config under the same lock as a network reload, so a reload running at the same time cannot leave the old groups in place. A failed update only warns, and the next reload or start catches up. Moving a VM between groups is an SDN change like putting a NIC on a vnet, which needs SDN.Use on the vnet. So a tag or name change that can move the VM also needs SDN.Use on /sdn/microseg, and so does creating, cloning or restoring a VM with such a tag or name. A destroy with purge removes the VM's own assignments from the policy, as it removes the VM from its backup and replication jobs. Assignments by tag or name are not the VM's and stay. Signed-off-by: Hannes Laimer --- src/PVE/API2/Qemu.pm | 39 +++++++++++++++++++++++++++++++++++ src/PVE/QemuServer.pm | 17 ++++++++++++++- src/PVE/QemuServer/Network.pm | 14 +++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm index 7830d09b66b1..4ab61029052d 100644 --- a/src/PVE/API2/Qemu.pm +++ b/src/PVE/API2/Qemu.pm @@ -26,6 +26,7 @@ use PVE::JSONSchema qw(get_standard_option); use PVE::RESTHandler; use PVE::ReplicationConfig; use PVE::GuestHelpers qw(assert_tag_permissions); +use PVE::Network::SDN::Microseg; use PVE::GuestImport; use PVE::QemuConfig; use PVE::QemuServer; @@ -1553,6 +1554,10 @@ __PACKAGE__->register_method({ my $vga = PVE::QemuServer::parse_vga($conf->{vga}); PVE::QemuServer::assert_clipboard_config($vga); + PVE::Network::SDN::Microseg::check_name_access( + $rpcenv, $authuser, undef, $conf->{name}, + ) if defined($conf->{name}); + PVE::QemuServer::Drive::warn_about_virtio_win_issues_in_config($conf); # auto generate uuid if user did not specify smbios1 option @@ -1580,6 +1585,9 @@ __PACKAGE__->register_method({ $vmid, {}, $conf->{tags}, $rpcenv, $authuser, ); $conf->{tags} = PVE::GuestHelpers::get_unique_tags($conf->{tags}); + PVE::Network::SDN::Microseg::check_tag_access( + $rpcenv, $authuser, undef, $conf->{tags}, + ); } PVE::QemuConfig->write_config($vmid, $conf); @@ -2418,8 +2426,11 @@ my $update_vm_api = sub { PVE::QemuConfig->write_config($vmid, $conf); } elsif ($opt eq 'tags') { assert_tag_permissions($vmid, $val, '', $rpcenv, $authuser); + PVE::Network::SDN::Microseg::check_tag_access($rpcenv, $authuser, $val, undef); delete $conf->{$opt}; PVE::QemuConfig->write_config($vmid, $conf); + PVE::QemuServer::Network::update_nets_enforcement($conf, $vmid) + if PVE::QemuServer::check_running($vmid); } elsif ($opt =~ m/^net\d+$/) { if ($conf->{$opt}) { PVE::QemuServer::check_bridge_access( @@ -2430,6 +2441,10 @@ my $update_vm_api = sub { } PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + } elsif ($opt eq 'name') { + PVE::Network::SDN::Microseg::check_name_access($rpcenv, $authuser, $val, undef); + PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); + PVE::QemuConfig->write_config($vmid, $conf); } else { PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -2547,6 +2562,12 @@ my $update_vm_api = sub { ); $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt}); + PVE::Network::SDN::Microseg::check_tag_access( + $rpcenv, + $authuser, + $conf->{$opt}, + $conf->{pending}->{$opt}, + ); } elsif ($opt =~ m/^net\d+$/) { if ($conf->{$opt}) { PVE::QemuServer::check_bridge_access( @@ -2587,6 +2608,11 @@ my $update_vm_api = sub { if $param->{cipassword} !~ /^\$(?:[156]|2[ay])(\$.+){2}/; } $conf->{cipassword} = $param->{cipassword}; + } elsif ($opt eq 'name') { + PVE::Network::SDN::Microseg::check_name_access( + $rpcenv, $authuser, $conf->{$opt}, $param->{$opt}, + ); + $conf->{pending}->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; @@ -2928,6 +2954,13 @@ __PACKAGE__->register_method({ print "purging VM $vmid from related configurations..\n"; PVE::ReplicationConfig::remove_vmid_jobs($vmid); PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid); + my $assignments = + eval { PVE::Network::SDN::Microseg::remove_guest_assignments($vmid); }; + warn "unable to remove the microseg assignments of VM $vmid: $@" if $@; + print + "NOTE: removed the microseg assignments of VM $vmid, pending an SDN" + . " apply.\n" + if $assignments; if ($ha_managed) { PVE::HA::Config::delete_service_from_config( @@ -4545,6 +4578,9 @@ __PACKAGE__->register_method({ PVE::QemuServer::check_mapping_access($rpcenv, $authuser, $oldconf); PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $oldconf); + PVE::Network::SDN::Microseg::check_tag_access( + $rpcenv, $authuser, undef, $oldconf->{tags}, + ) if defined($oldconf->{tags}); $check_cpu_model_access->($rpcenv, $authuser, $oldconf); @@ -4658,6 +4694,9 @@ __PACKAGE__->register_method({ } else { $newconf->{name} = "Copy-of-VM-" . ($oldconf->{name} // $vmid); } + PVE::Network::SDN::Microseg::check_name_access( + $rpcenv, $authuser, undef, $newconf->{name}, + ); if ($param->{description}) { $newconf->{description} = $param->{description}; diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 69a2bd64f849..ef4e5a6f9855 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -35,6 +35,7 @@ use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne); use PVE::Mapping::Dir; use PVE::Mapping::PCI; use PVE::Mapping::USB; +use PVE::Network::SDN::Microseg; use PVE::Network::SDN::Vnets; use PVE::INotify; use PVE::JSONSchema qw(get_standard_option parse_property_string); @@ -4376,6 +4377,11 @@ sub foreach_volid { } } +my $microseg_option = { + 'name' => 1, + 'tags' => 1, +}; + my $fast_plug_option = { 'description' => 1, 'hookscript' => 1, @@ -4488,12 +4494,14 @@ sub vmconfig_hotplug_pending { }; my $changes = 0; + my $microseg_relevant = 0; foreach my $opt (keys %{ $conf->{pending} }) { # add/change if ($fast_plug_option->{$opt}) { my $new = delete $conf->{pending}->{$opt}; $cloudinit_record_changed->($conf, $opt, $conf->{$opt}, $new); $conf->{$opt} = $new; $changes = 1; + $microseg_relevant = 1 if $microseg_option->{$opt}; } } @@ -4564,7 +4572,7 @@ sub vmconfig_hotplug_pending { my $balloon = get_current_memory($conf->{memory}); mon_cmd($vmid, "balloon", value => $balloon * 1024 * 1024); } elsif ($fast_plug_option->{$opt}) { - # do nothing + $microseg_relevant = 1 if $microseg_option->{$opt}; } elsif ($opt =~ m/^net(\d+)$/) { die "skip\n" if !$hotplug_features->{network}; vm_deviceunplug($vmid, $conf, $opt); @@ -4751,6 +4759,9 @@ sub vmconfig_hotplug_pending { PVE::QemuConfig->write_config($vmid, $conf); + # reads the tags and name from pmxcfs, so after the write + PVE::QemuServer::Network::update_nets_enforcement($conf, $vmid) if $microseg_relevant; + if ($hotplug_features->{cloudinit} && PVE::QemuServer::Cloudinit::has_changes($conf)) { PVE::QemuServer::vmconfig_update_cloudinit_drive($storecfg, $conf, $vmid); } @@ -6320,6 +6331,10 @@ sub check_restore_permissions { check_bridge_access($rpcenv, $user, $conf); check_mapping_access($rpcenv, $user, $conf); + PVE::Network::SDN::Microseg::check_tag_access($rpcenv, $user, undef, $conf->{tags}) + if defined($conf->{tags}); + PVE::Network::SDN::Microseg::check_name_access($rpcenv, $user, undef, $conf->{name}) + if defined($conf->{name}); } # vzdump restore implementation diff --git a/src/PVE/QemuServer/Network.pm b/src/PVE/QemuServer/Network.pm index b4893c015646..ebde9621fbf8 100644 --- a/src/PVE/QemuServer/Network.pm +++ b/src/PVE/QemuServer/Network.pm @@ -355,6 +355,20 @@ sub tap_plug { PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate); } +# the groups of a NIC depend on the VM's tags and name +sub update_nets_enforcement { + my ($conf, $vmid) = @_; + + for my $opt (keys %$conf) { + next if $opt !~ m/^net(\d+)$/; + my $iface = "tap${vmid}i$1"; + next if !-d "/sys/class/net/$iface"; + my $net = parse_net($conf->{$opt}) or next; + my $firewall = $net->{firewall} && PVE::Firewall::Helpers::needs_fwbr($net->{bridge}); + PVE::Network::SDN::Zones::tap_update($iface, $firewall); + } +} + sub get_nets_host_mtu { my ($vmid, $conf) = @_; -- 2.47.3