public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Leo Nunner <l.nunner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v2 firewall] fix #4204: automatically update usages of group when it is renamed
Date: Wed, 28 Sep 2022 11:11:44 +0200	[thread overview]
Message-ID: <20220928091144.40064-1-l.nunner@proxmox.com> (raw)

When renaming a group, the usages didn't get updated automatically. To
get around problems with atomicity, the old rule is first cloned with the
new name, the usages are updated and only when updating has finished, the
old rule is deleted.

The subroutines that lock/update host configs had to be changed so that
it's possible to lock any config, not just the one of the current host.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
Changes from v1:
    - change filter mechanism for updating rules
    - factor out hash accessess into separate variables
    - write host firewall config with the cloned group immediately

 src/PVE/API2/Firewall/Groups.pm | 94 +++++++++++++++++++++++++++------
 src/PVE/API2/Firewall/Host.pm   |  2 +-
 src/PVE/API2/Firewall/Rules.pm  |  2 +-
 src/PVE/Firewall.pm             | 16 +++---
 4 files changed, 91 insertions(+), 23 deletions(-)

diff --git a/src/PVE/API2/Firewall/Groups.pm b/src/PVE/API2/Firewall/Groups.pm
index 558ba8e..22da9a8 100644
--- a/src/PVE/API2/Firewall/Groups.pm
+++ b/src/PVE/API2/Firewall/Groups.pm
@@ -30,6 +30,15 @@ my $get_security_group_list = sub {
     return wantarray ? ($list, $digest) : $list;
 };
 
+my $rename_fw_rules = sub {
+    my ($old, $new, $rules) = @_;
+
+    for my $rule (@{$rules}) {
+	next if ($rule->{type} ne "group" || $rule->{action} ne $old);
+	$rule->{action} = $new;
+    }
+};
+
 __PACKAGE__->register_method({
     name => 'list_security_groups',
     path => '',
@@ -91,35 +100,90 @@ __PACKAGE__->register_method({
     code => sub {
 	my ($param) = @_;
 
+	my $group = $param->{group};
+	my $rename = $param->{rename};
+	my $comment = $param->{comment};
+
 	PVE::Firewall::lock_clusterfw_conf(10, sub {
 	    my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
 
-	    if ($param->{rename}) {
+	    if ($rename) {
 		my (undef, $digest) = &$get_security_group_list($cluster_conf);
 		PVE::Tools::assert_if_modified($digest, $param->{digest});
 
-		raise_param_exc({ group => "Security group '$param->{rename}' does not exist" })
-		    if !$cluster_conf->{groups}->{$param->{rename}};
+		raise_param_exc({ group => "Security group '$rename' does not exist" })
+		    if !$cluster_conf->{groups}->{$rename};
 
 		# prevent overwriting an existing group
-		raise_param_exc({ group => "Security group '$param->{group}' does already exist" })
-		    if $cluster_conf->{groups}->{$param->{group}} &&
-		    $param->{group} ne $param->{rename};
-
-		my $data = delete $cluster_conf->{groups}->{$param->{rename}};
-		$cluster_conf->{groups}->{$param->{group}} = $data;
-		if (my $comment = delete $cluster_conf->{group_comments}->{$param->{rename}}) {
-		    $cluster_conf->{group_comments}->{$param->{group}} = $comment;
+		raise_param_exc({ group => "Security group '$group' does already exist" })
+		    if $cluster_conf->{groups}->{$group} &&
+		    $group ne $rename;
+
+		if ($rename eq $group) {
+		   $cluster_conf->{group_comments}->{$rename} = $comment if defined($comment);
+		    PVE::Firewall::save_clusterfw_conf($cluster_conf);
+		    return;
 		}
-		$cluster_conf->{group_comments}->{$param->{group}} = $param->{comment} if defined($param->{comment});
+
+		# Create an exact copy of the old security group
+		$cluster_conf->{groups}->{$group} = $cluster_conf->{groups}->{$rename};
+		$cluster_conf->{group_comments}->{$group} = $cluster_conf->{group_comments}->{$rename};
+
+		# Update comment if provided
+		$cluster_conf->{group_comments}->{$group} = $comment if defined($comment);
+
+		# Write the copy to the cluster config, so that if something fails inbetween, the new firewall
+		# rules won't be broken when the new name is referenced
+		PVE::Firewall::save_clusterfw_conf($cluster_conf);
+
+		# Update all the host configs to the new copy
+		my $hosts = PVE::Cluster::get_nodelist();
+		foreach my $host (@$hosts) {
+		    PVE::Firewall::lock_hostfw_conf($host, 10, sub {
+		        my $host_conf_path = "/etc/pve/nodes/$host/host.fw";
+		        my $host_conf = PVE::Firewall::load_hostfw_conf($cluster_conf, $host_conf_path);
+
+			if(defined($host_conf)) {
+			    &$rename_fw_rules($rename,
+			        $group,
+			        $host_conf->{rules});
+			    PVE::Firewall::save_hostfw_conf($host_conf, $host_conf_path);
+		        }
+		    });
+		}
+
+		# Update all the VM configs
+		my $vms = PVE::Cluster::get_vmlist();
+		foreach my $vm (keys %{$vms->{ids}}) {
+		    PVE::Firewall::lock_vmfw_conf($vm, 10, sub {
+		        my $vm_type = $vms->{ids}->{$vm}->{type} eq "lxc" ? "ct" : "vm";
+		        my $vm_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, $vm_type, $vm, "/etc/pve/firewall");
+
+			if (defined($vm_conf)) {
+			    &$rename_fw_rules($rename,
+			        $group,
+			        $vm_conf->{rules});
+			    PVE::Firewall::save_vmfw_conf($vm, $vm_conf);
+			}
+		    });
+		}
+
+		# And also update the cluster itself
+		&$rename_fw_rules($rename,
+		    $group,
+		    $cluster_conf->{rules});
+
+		# Now that everything has been updated, the old rule can be deleted
+		delete $cluster_conf->{groups}->{$rename};
+		delete $cluster_conf->{group_comments}->{$rename};
 	    } else {
 		foreach my $name (keys %{$cluster_conf->{groups}}) {
 		    raise_param_exc({ group => "Security group '$name' already exists" })
-			if $name eq $param->{group};
+			if $name eq $group;
 		}
 
-		$cluster_conf->{groups}->{$param->{group}} = [];
-		$cluster_conf->{group_comments}->{$param->{group}} = $param->{comment} if defined($param->{comment});
+		$cluster_conf->{groups}->{$group} = [];
+		$cluster_conf->{group_comments}->{$group} = $comment if defined($comment);
 	    }
 
 	    PVE::Firewall::save_clusterfw_conf($cluster_conf);
diff --git a/src/PVE/API2/Firewall/Host.pm b/src/PVE/API2/Firewall/Host.pm
index b66ca55..dfeccd0 100644
--- a/src/PVE/API2/Firewall/Host.pm
+++ b/src/PVE/API2/Firewall/Host.pm
@@ -118,7 +118,7 @@ __PACKAGE__->register_method({
     code => sub {
 	my ($param) = @_;
 
-	PVE::Firewall::lock_hostfw_conf(10, sub {
+	PVE::Firewall::lock_hostfw_conf(undef, 10, sub {
 	    my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
 	    my $hostfw_conf = PVE::Firewall::load_hostfw_conf($cluster_conf);
 
diff --git a/src/PVE/API2/Firewall/Rules.pm b/src/PVE/API2/Firewall/Rules.pm
index 4475663..9fcfb20 100644
--- a/src/PVE/API2/Firewall/Rules.pm
+++ b/src/PVE/API2/Firewall/Rules.pm
@@ -522,7 +522,7 @@ sub rule_env {
 sub lock_config {
     my ($class, $param, $code) = @_;
 
-    PVE::Firewall::lock_hostfw_conf(10, $code, $param);
+    PVE::Firewall::lock_hostfw_conf(undef, 10, $code, $param);
 }
 
 sub load_config {
diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index c56e448..e6d6802 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -3624,10 +3624,12 @@ sub save_clusterfw_conf {
     }
 }
 
-sub lock_hostfw_conf {
-    my ($timeout, $code, @param) = @_;
+sub lock_hostfw_conf : prototype($$$@) {
+    my ($node, $timeout, $code, @param) = @_;
+
+    $node = $nodename if !defined($node);
 
-    my $res = PVE::Cluster::cfs_lock_firewall("host-$nodename", $timeout, $code, @param);
+    my $res = PVE::Cluster::cfs_lock_firewall("host-$node", $timeout, $code, @param);
     die $@ if $@;
 
     return $res;
@@ -3643,7 +3645,9 @@ sub load_hostfw_conf {
 }
 
 sub save_hostfw_conf {
-    my ($hostfw_conf) = @_;
+    my ($hostfw_conf, $filename) = @_;
+
+    $filename = $hostfw_conf_filename if !defined($filename);
 
     my $raw = '';
 
@@ -3658,9 +3662,9 @@ sub save_hostfw_conf {
     }
 
     if ($raw) {
-	PVE::Tools::file_set_contents($hostfw_conf_filename, $raw);
+	PVE::Tools::file_set_contents($filename, $raw);
     } else {
-	unlink $hostfw_conf_filename;
+	unlink $filename;
     }
 }
 
-- 
2.30.2





             reply	other threads:[~2022-09-28  9:13 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-28  9:11 Leo Nunner [this message]
2022-10-04 11:18 ` [pve-devel] applied: " Wolfgang Bumiller

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=20220928091144.40064-1-l.nunner@proxmox.com \
    --to=l.nunner@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