all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-firewall v4 12/27] firewall: references: handle security groups
Date: Thu, 22 Oct 2026 14:46:53 +0200	[thread overview]
Message-ID: <20261022124708.429069-13-a.bied-charreton@proxmox.com> (raw)
In-Reply-To: <20261022124708.429069-1-a.bied-charreton@proxmox.com>

Security groups may be referenced by the action of a rule, rather than
the source, dest and cidr fields that ipsets and aliases are referenced
in.

Move field lists into the object spec and let a spec limit its
references to a single rule type so a group named 'drop' is not matched
by the action of a normal DROP rule.

Groups may only be defined in the cluster config so they cannot be
shadowed and references to them are always unscoped.

Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
 src/PVE/Firewall/References.pm | 52 +++++++++++++++++++++-------------
 src/test/test_update_refs.pl   | 38 +++++++++++++++++++++++++
 2 files changed, 70 insertions(+), 20 deletions(-)

diff --git a/src/PVE/Firewall/References.pm b/src/PVE/Firewall/References.pm
index f32c7db..67ba4d0 100644
--- a/src/PVE/Firewall/References.pm
+++ b/src/PVE/Firewall/References.pm
@@ -143,27 +143,37 @@ sub rewrite_refs_in_env {
     return $changes;
 }
 
-# field name + value prefix for firewall object references
+# how a reference to a firewall object looks and where it is found:
+#
+#   prefix: the prefix a reference carries
+#   self:   the section a config defines the object in, used to resolve shadowing
+#   fields: the fields that can hold a reference
+#   type:   the rule type references are limited to
 my $object_ref_specs = {
-    ipset => { prefix => '+', self => 'ipset' },
-    aliases => { prefix => '', self => 'aliases' },
+    ipset => { prefix => '+', self => 'ipset', fields => ['source', 'dest', 'cidr'] },
+    aliases => { prefix => '', self => 'aliases', fields => ['source', 'dest', 'cidr'] },
+    # a security group may only be referenced by the action of the rule invoking it. matching
+    # any other rule could rewrite a DROP action when renaming a group named 'drop', for example.
+    groups => { prefix => '', self => 'groups', fields => ['action'], type => 'group' },
 };
 
-# fields to check for object references
-my $ref_fields = ['source', 'dest', 'cidr'];
+=head3 matched_ref($item, $ctx)
 
-=head3 matched_ref($item, $repl)
-
-Return the reference C<$item> holds that C<$repl> has an entry for, or C<undef> if there is none.
+Return the reference C<$item> holds that the replacement map of C<$ctx> has an entry for, or
+C<undef> if there is none.
 
 =cut
 
 sub matched_ref {
-    my ($item, $repl) = @_;
+    my ($item, $ctx) = @_;
+
+    my $spec = $ctx->{spec};
 
-    for my $field ($ref_fields->@*) {
+    return undef if $spec->{type} && ($item->{type} // '') ne $spec->{type};
+
+    for my $field ($spec->{fields}->@*) {
         my $ref = lc($item->{$field} // '');
-        return $ref if exists($repl->{$ref});
+        return $ref if exists($ctx->{repl}->{$ref});
     }
 
     return undef;
@@ -195,7 +205,7 @@ sub rewrite_items {
     for my $pos (0 .. $items->$#*) {
         my $item = $items->[$pos];
 
-        my $ref = matched_ref($item, $repl);
+        my $ref = matched_ref($item, $ctx);
         if (!defined($ref)) {
             # no match, keep item
             push @result, $item;
@@ -215,7 +225,7 @@ sub rewrite_items {
         next if $remove;
 
         if ($action eq 'rename') {
-            for my $field ($ref_fields->@*) {
+            for my $field ($ctx->{spec}->{fields}->@*) {
                 my $value = lc($item->{$field} // '');
                 $item->{$field} = $repl->{$value} if exists($repl->{$value});
             }
@@ -229,7 +239,7 @@ sub rewrite_items {
     return \@result;
 }
 
-=head3 build_ref_map($op, $conf, $env, $is_guest)
+=head3 build_ref_map($op, $spec, $conf, $env, $is_guest)
 
 Map every allowed reference to the objects in C<$op> that resolves in C<$conf> to what it should
 become, i.e. either the renamed reference or C<undef> if C<$op> has no new name.
@@ -252,9 +262,7 @@ a guest-local or a cluster object
 =cut
 
 sub build_ref_map {
-    my ($op, $conf, $env, $is_guest) = @_;
-
-    my $spec = $object_ref_specs->{ $op->{kind} } // die "unknown object kind '$op->{kind}'\n";
+    my ($op, $spec, $conf, $env, $is_guest) = @_;
 
     my $prefix = $spec->{prefix};
     my $self = $spec->{self};
@@ -305,8 +313,11 @@ Only exposed for testing, see C<update_refs>'s POD for details.
 sub rewrite_refs_in_conf {
     my ($conf, $op, $env, $is_guest) = @_;
 
+    my $spec = $object_ref_specs->{ $op->{kind} } // die "unknown object kind '$op->{kind}'\n";
+
     my $ctx = {
-        repl => build_ref_map($op, $conf, $env, $is_guest),
+        spec => $spec,
+        repl => build_ref_map($op, $spec, $conf, $env, $is_guest),
         action => $op->{action},
         changes => [],
     };
@@ -341,7 +352,7 @@ and renames are written back lowercased.
 C<$opts> holds:
 
     conf     # the firewall config the object is defined in
-    kind     # 'ipset' or 'aliases'
+    kind     # 'ipset', 'aliases' or 'groups'
     old      # the name the references carry, or an arrayref of names to disable or drop
     new      # the name to rewrite them to, only used when renaming
     env      # 'cluster', 'sdn', or 'vm'/'ct' for a guest-defined object
@@ -352,7 +363,8 @@ C<$opts> holds:
 
 The caller is responsible for locking and saving C<conf> itself.
 
-IPSet members have no disabled state, so C<disable> removes them as well.
+A security group may only be referenced by the action of a rule so those references are confined to
+rules. IPSet members have no disabled state, so C<disable> removes them as well.
 
 If C<conf> is the cluster config, i.e. if C<env> is C<cluster> or C<sdn>, guest, host and vnet
 configs are sequentially locked, updated and saved. C<save> is called once the references in
diff --git a/src/test/test_update_refs.pl b/src/test/test_update_refs.pl
index f148eed..359c4c4 100755
--- a/src/test/test_update_refs.pl
+++ b/src/test/test_update_refs.pl
@@ -325,6 +325,44 @@ subtest 'rename changes record the new reference' => sub {
     );
 };
 
+subtest 'groups: rename rewrites the action of group rules' => sub {
+    my $conf = {
+        rules => [
+            { type => 'group', action => 'web' },
+            { type => 'in', action => 'ACCEPT', source => 'web' },
+        ],
+        groups => {
+            web => [{ type => 'in', action => 'ACCEPT' }],
+        },
+        ipset => {
+            set => [{ cidr => 'web' }],
+        },
+    };
+
+    my $changes = rewrite($conf, 'groups', 'web', 'www', 'cluster', 0);
+
+    is($conf->{rules}->[0]->{action}, 'www', 'group rule points at the new name');
+    is($conf->{rules}->[1]->{action}, 'ACCEPT', 'a plain rule keeps its action');
+    is($conf->{rules}->[1]->{source}, 'web', 'a source matching the group name is left alone');
+    is($conf->{ipset}->{set}->[0]->{cidr}, 'web', 'an IPSet member is left alone');
+    is(scalar($changes->@*), 1, 'only the group rule is reported');
+};
+
+subtest 'groups: a group named after an action does not match plain rules' => sub {
+    my $conf = {
+        rules => [
+            { type => 'out', action => 'DROP' }, { type => 'group', action => 'drop' },
+        ],
+    };
+
+    my $changes = rewrite($conf, 'groups', 'drop', undef, 'cluster', 0);
+
+    is($conf->{rules}->[0]->{action}, 'DROP', 'the DROP rule is untouched');
+    ok(!$conf->{rules}->[0]->{enable}, 'the DROP rule is not disabled');
+    is($conf->{rules}->[1]->{enable}, 0, 'the group rule is disabled');
+    is(scalar($changes->@*), 1, 'only the group rule is reported');
+};
+
 subtest 'update_refs: rejects invalid argument combinations' => sub {
     my $run = sub {
         my (%args) = @_;
-- 
2.47.3





  parent reply	other threads:[~2026-10-22 12:49 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 12:46 [PATCH container/docs/firewall/manager/network/qemu-server v4 00/27] handle dangling references when firewall objects go away Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 01/27] firewall: move object reference helpers to their own module Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 02/27] firewall: references: add helpers to update object references Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 03/27] firewall: references: log the references an object update touched Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 04/27] firewall: references: add wrappers for the SDN-generated IPSets Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 05/27] firewall: parser: do not log errors for disabled rules Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 06/27] firewall: ipset: add option to handle dangling references on rename Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 07/27] firewall: ipset: add option to handle dangling references on delete Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 08/27] firewall: aliases: add option to handle dangling references on rename Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 09/27] firewall: aliases: add option to handle dangling references on delete Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 10/27] firewall: api: run reference updates as tasks Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 11/27] firewall: tests: add tests for object reference updates Arthur Bied-Charreton
2026-10-22 12:46 ` Arthur Bied-Charreton [this message]
2026-10-22 12:46 ` [PATCH pve-firewall v4 13/27] firewall: groups: add option to handle dangling references on rename Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-firewall v4 14/27] fix #8176: firewall: groups: handle dangling references on delete Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-network v4 15/27] apply: add option to handle dangling references on VNet deletion Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH qemu-server v4 16/27] api: destroy_vm: add option to handle dangling IPSet references Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-container v4 17/27] " Arthur Bied-Charreton
2026-10-22 12:46 ` [PATCH pve-docs v4 18/27] firewall: remove trailing spaces Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-docs v4 19/27] firewall: document renaming and removing objects Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 20/27] ui: firewall: add a common widget for handling dangling references Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 21/27] ui: firewall: ipset: add controls for " Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 22/27] ui: firewall: aliases: " Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 23/27] ui: sdn: apply: add control for dangling IPSet references Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 24/27] ui: guest destroy: use let for non-constant variable bindings Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 25/27] ui: guest destroy: add control for dangling IPSet references Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 26/27] ui: firewall: show the task for reference updates Arthur Bied-Charreton
2026-10-22 12:47 ` [PATCH pve-manager v4 27/27] ui: firewall: groups: add controls for dangling references Arthur Bied-Charreton

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=20261022124708.429069-13-a.bied-charreton@proxmox.com \
    --to=a.bied-charreton@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal