[pve-devel] [PATCH v2 firewall 2/2] fix #4414: automatically rename usages of aliases and IPsets
Leo Nunner
l.nunner at proxmox.com
Thu Jan 26 15:31:27 CET 2023
Renaming an alias or an IPset broke existing firewall rules, both
standalone and as part of a security group. This was already fixed with
security groups (see issue #4204), so it was only a matter of factoring
out the relevant code and providing a more general implementation, so
that it could be reused for the other components.
The mechanism works the same for all three components: first, the
existing object is duplicated with a new name. Then, all the occurences
inside rules are replaced with the new name, and, when this is done, the
old object is deleted. This ensures that a failure while updating (such
as a timeout due to the inability to lock a config file) does not break
the rules that could not be updated yet.
In PVE::Firewall::Helpers, a function is now provided to globally update
all configs (nodes, VMs, CTs) using a custom function that is passed as
a parameter. Each config is locked, passed to the function and then
written.
Signed-off-by: Leo Nunner <l.nunner at proxmox.com>
---
src/PVE/API2/Firewall/Aliases.pm | 52 +++++++++++++++++++++++++++---
src/PVE/API2/Firewall/Groups.pm | 54 ++++++--------------------------
src/PVE/API2/Firewall/IPSet.pm | 47 +++++++++++++++++++++++++--
src/PVE/Firewall/Helpers.pm | 33 +++++++++++++++++++
4 files changed, 135 insertions(+), 51 deletions(-)
diff --git a/src/PVE/API2/Firewall/Aliases.pm b/src/PVE/API2/Firewall/Aliases.pm
index c84c348..4c0163f 100644
--- a/src/PVE/API2/Firewall/Aliases.pm
+++ b/src/PVE/API2/Firewall/Aliases.pm
@@ -249,15 +249,59 @@ sub register_update_alias {
raise_param_exc({ name => "no such alias" }) if !$aliases->{$rename_from};
- my $data = { name => $rename_from, cidr => $param->{cidr} // $aliases->{$rename_from}->{cidr} };
- $data->{comment} = $param->{comment} if $param->{comment};
-
- $aliases->{$rename_from} = $data;
+ my $alias = $aliases->{$rename_from};
+ $alias->{cidr} = $param->{cidr} if defined($param->{cidr});
+ $alias->{comment} = $param->{comment} if defined($param->{comment});
if ($rename_to && ($rename_from ne $rename_to)) {
raise_param_exc({ name => "alias '$param->{rename}' already exists" })
if grep { lc($_) eq lc($rename_to) } (keys $aliases->%*);
$aliases->{$rename_to} = $aliases->{$rename_from};
+ $class->save_aliases($param, $fw_conf, $aliases);
+
+ my $rename_fw_rules = sub {
+ my ($config) = @_;
+
+ for my $rule ($config->{rules}->@*) {
+ next if $rule->{type} eq "group";
+ $rule->{source} = $rename_to if lc($rule->{source}) eq lc($rename_from);
+ $rule->{dest} = $rename_to if lc($rule->{dest}) eq lc($rename_from);
+ }
+ };
+
+ my $rename_ipsets = sub {
+ my ($config) = @_;
+
+ for my $ipset (values $config->{ipset}->%*) {
+ for my $ip ($ipset->{entries}->@*) {
+ $ip->{cidr} = $rename_to if lc($ip->{cidr}) eq lc($rename_from);
+ }
+ }
+ };
+
+ # Figure out scope
+ if ($class->rule_env() eq "cluster") {
+ # Update rules and IPsets
+ PVE::Firewall::Helpers::global_update_configs($fw_conf, sub {
+ my ($config) = @_;
+
+ $rename_fw_rules->($config);
+ $rename_ipsets->($config);
+ });
+
+ # Update group definitions
+ for my $group (values $fw_conf->{groups}->%*) {
+ for my $rule ($group->{rules}->@*) {
+ $rule->{source} = $rename_to if lc($rule->{source}) eq lc($rename_from);
+ $rule->{dest} = $rename_to if lc($rule->{dest}) eq lc($rename_from);
+ }
+ }
+ }
+
+ $rename_fw_rules->($fw_conf);
+ $rename_ipsets->($fw_conf);
+
+ # Now we can delete the old one
delete $aliases->{$rename_from};
}
diff --git a/src/PVE/API2/Firewall/Groups.pm b/src/PVE/API2/Firewall/Groups.pm
index 3bf01ac..524fd46 100644
--- a/src/PVE/API2/Firewall/Groups.pm
+++ b/src/PVE/API2/Firewall/Groups.pm
@@ -28,15 +28,6 @@ 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 => '',
@@ -131,44 +122,19 @@ __PACKAGE__->register_method({
# 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_from,
- $rename_to,
- $host_conf->{rules});
- PVE::Firewall::save_hostfw_conf($host_conf, $host_conf_path);
- }
- });
- }
+ my $rename_fw_rules = sub {
+ my ($config) = @_;
- # 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_from,
- $rename_to,
- $vm_conf->{rules});
- PVE::Firewall::save_vmfw_conf($vm, $vm_conf);
- }
- });
- }
+ for my $rule (@{$config->{rules}}) {
+ next if ($rule->{type} ne "group" || $rule->{action} ne $rename_from);
+ $rule->{action} = $rename_to;
+ }
+ };
- # And also update the cluster itself
- &$rename_fw_rules($rename_from,
- $rename_to,
- $cluster_conf->{rules});
+ # Update VM/CT/cluster rules
+ PVE::Firewall::Helpers::global_update_configs($cluster_conf, $rename_fw_rules);
+ $rename_fw_rules->($cluster_conf);
- # Now that everything has been updated, the old rule can be deleted
delete $cluster_conf->{groups}->{$rename_from};
} else {
# In this context, $rename_to is the name for the new group
diff --git a/src/PVE/API2/Firewall/IPSet.pm b/src/PVE/API2/Firewall/IPSet.pm
index aae9006..6e0829a 100644
--- a/src/PVE/API2/Firewall/IPSet.pm
+++ b/src/PVE/API2/Firewall/IPSet.pm
@@ -642,6 +642,10 @@ sub register_create {
code => sub {
my ($param) = @_;
+ my $rename_to = $param->{name};
+ my $rename_from = $param->{rename};
+ my $comment = $param->{comment};
+
$class->lock_config($param, sub {
my ($param) = @_;
@@ -663,9 +667,46 @@ sub register_create {
if (grep { lc($_) eq lc($rename_to) } (keys $fw_conf->{ipset}->%*)) &&
$rename_to ne $rename_from;
- my $data = delete $fw_conf->{ipset}->{$rename_from};
- $fw_conf->{ipset}->{$rename_to} = $data;
- $fw_conf->{ipset}->{$rename_to}->{comment} = $param->{comment} if defined($param->{comment});
+ if ($rename_from eq $rename_to) {
+ $fw_conf->{ipset}->{$rename_from}->{comment} = $comment if defined($comment);
+ $class->save_config($param, $fw_conf);
+ return;
+ }
+
+ # Create a reference to the old IPset, so that both the old and the new name point to the same set
+ $fw_conf->{ipset}->{$rename_to} = $fw_conf->{ipset}->{$rename_from};
+
+ # Update comment if provided
+ $fw_conf->{ipset}->{$rename_to}->{comment} = $comment if defined($comment);
+
+ $class->save_config($param, $fw_conf);
+
+ my $rename_fw_rules = sub {
+ my ($config) = @_;
+
+ for my $rule (@{$config->{rules}}) {
+ next if $rule->{type} eq "group";
+ $rule->{source} = "+$rename_to" if $rule->{source} eq "+$rename_from";
+ $rule->{dest} = "+$rename_to" if $rule->{dest} eq "+$rename_from";
+ }
+ };
+
+ # Figure out scope
+ if ($class->rule_env() eq "cluster") {
+ PVE::Firewall::Helpers::global_update_configs($fw_conf, $rename_fw_rules);
+
+ # Update group definitions
+ for my $group (values $fw_conf->{groups}->%*) {
+ for my $rule ($group->{rules}->@*) {
+ $rule->{source} = "+$rename_to" if $rule->{source} eq "+$rename_from";
+ $rule->{dest} = "+$rename_to" if $rule->{dest} eq "+$rename_from";
+ }
+ }
+ }
+
+ $rename_fw_rules->($fw_conf);
+
+ delete $fw_conf->{ipset}->{$rename_from};
} else {
# In this context, $rename_to is the name for the new IPSet
raise_param_exc({ name => "IPSet '$rename_to' already exists" })
diff --git a/src/PVE/Firewall/Helpers.pm b/src/PVE/Firewall/Helpers.pm
index 154fca5..3137b33 100644
--- a/src/PVE/Firewall/Helpers.pm
+++ b/src/PVE/Firewall/Helpers.pm
@@ -11,6 +11,7 @@ our @EXPORT_OK = qw(
lock_vmfw_conf
remove_vmfw_conf
clone_vmfw_conf
+global_update_configs
);
my $pvefw_conf_dir = "/etc/pve/firewall";
@@ -52,4 +53,36 @@ sub clone_vmfw_conf {
});
}
+# Updates all VM and CT configs. For every host/VM/CT, a custom function
+# $fun is called with the config object as a parameter.
+sub global_update_configs {
+ my ($cluster_conf, $fun) = @_;
+
+ 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)) {
+ $fun->($host_conf);
+ PVE::Firewall::save_hostfw_conf($host_conf, $host_conf_path);
+ }
+ });
+ }
+
+ 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)) {
+ $fun->($vm_conf);
+ PVE::Firewall::save_vmfw_conf($vm, $vm_conf);
+ }
+ });
+ }
+}
+
1;
--
2.30.2
More information about the pve-devel
mailing list