Am 15.11.24 um 13:09 schrieb Stefan Hanreich: > This also includes support for parsing rules referencing IPSets in the > new SDN scope and generating those IPSets in the firewall. > > Loading SDN configuration is optional, since loading it requires root > privileges which we do not have in all call sites. Adding the flag > allows us to selectively load the SDN configuration only where > required and at the same time allows us to only elevate privileges in > the API where absolutely needed. > > Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> > Reviewed-by: Wolfgang Bumiller <w.bumil...@proxmox.com> > Tested-By: Gabriel Goller <g.gol...@proxmox.com> > Tested-By: Hannes Dürr <h.du...@proxmox.com> > --- > src/PVE/Firewall.pm | 62 +++++++++++++++++++++++++++++---- > src/PVE/Service/pve_firewall.pm | 4 +-- > 2 files changed, 57 insertions(+), 9 deletions(-) > > diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm > index 09544ba..7642bf6 100644 > --- a/src/PVE/Firewall.pm > +++ b/src/PVE/Firewall.pm > @@ -25,6 +25,7 @@ use PVE::Tools qw($IPV4RE $IPV6RE); > use PVE::Tools qw(run_command lock_file dir_glob_foreach); > > use PVE::Firewall::Helpers; > +use PVE::RS::Firewall::SDN; > > my $pvefw_conf_dir = "/etc/pve/firewall"; > my $clusterfw_conf_filename = "$pvefw_conf_dir/cluster.fw"; > @@ -1689,9 +1690,12 @@ sub verify_rule { > > if (my $value = $rule->{$name}) { > if ($value =~ m/^\+/) { > - if ($value =~ m@^\+(guest/|dc/)?(${ipset_name_pattern})$@) { > + if ($value =~ m@^\+(guest/|dc/|sdn/)?(${ipset_name_pattern})$@) > { > &$add_error($name, "no such ipset '$2'") > - if !($cluster_conf->{ipset}->{$2} || ($fw_conf && > $fw_conf->{ipset}->{$2})); > + if !($cluster_conf->{ipset}->{$2} > + || ($fw_conf && $fw_conf->{ipset}->{$2}) > + || ($cluster_conf->{sdn} && > $cluster_conf->{sdn}->{ipset}->{$2}) > + || ($fw_conf->{sdn} && > $fw_conf->{sdn}->{ipset}->{$2}));
Avoid multi-line post-ifs: https://pve.proxmox.com/wiki/Perl_Style_Guide#Wrapping_Post-If rather use something like if ( !$cluster_conf->{ipset}->{$2} && !($fw_conf && $fw_conf->{ipset}->{$2}) && !($cluster_conf->{sdn} && $cluster_conf->{sdn}->{ipset}->{$2}) && !($fw_conf->{sdn} && $fw_conf->{sdn}->{ipset}->{$2})) ) { $add_error->($name, "no such ipset '$2'") } > > } else { > &$add_error($name, "invalid ipset name '$value'"); > @@ -2108,13 +2112,15 @@ sub ipt_gen_src_or_dst_match { > > my $match; > if ($adr =~ m/^\+/) { > - if ($adr =~ m@^\+(guest/|dc/)?(${ipset_name_pattern})$@) { > + if ($adr =~ m@^\+(guest/|dc/|sdn/)?(${ipset_name_pattern})$@) { > my $scope = $1 // ""; > my $name = $2; A small closer could help to reduce bewlow if expressions, e.g. like: my $is_scope = sub { return !$scope || $scope eq "$_[0]/" }; #... if ($is_scope->('guest') && $fw_conf && $fw_conf->{ipset}->{$name}) { #... > my $ipset_chain; > - if ($scope ne 'dc/' && $fw_conf && $fw_conf->{ipset}->{$name}) { > + if ((!$scope || $scope eq 'guest/') && $fw_conf && > $fw_conf->{ipset}->{$name}) { > $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, > $name, $ipversion); > - } elsif ($scope ne 'guest/' && $cluster_conf && > $cluster_conf->{ipset}->{$name}) { > + } elsif ((!$scope || $scope eq 'dc/') && $cluster_conf && > $cluster_conf->{ipset}->{$name}) { > + $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); > + } elsif ((!$scope || $scope eq 'sdn/') && $cluster_conf->{sdn} && > $cluster_conf->{sdn}->{ipset}->{$name}) { > $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); > } else { > die "no such ipset '$name'\n"; > @@ -3644,7 +3650,8 @@ sub lock_clusterfw_conf { > } > > sub load_clusterfw_conf { > - my ($filename) = @_; > + my ($filename, $options) = @_; > + > > $filename = $clusterfw_conf_filename if !defined($filename); > my $empty_conf = { > @@ -3655,6 +3662,7 @@ sub load_clusterfw_conf { > group_comments => {}, > ipset => {} , > ipset_comments => {}, > + sdn => load_sdn_conf(), it's a bit odd to assign the full SDN related config to a variable named $empty_config, but assigning it after the parser will cause a semantic difference for the case where the firewall config is empty, not sure if that is fine. > }; > > my $cluster_conf = generic_fw_config_parser($filename, $empty_conf, > $empty_conf, 'cluster'); > @@ -3663,6 +3671,45 @@ sub load_clusterfw_conf { > return $cluster_conf; > } > > +sub load_sdn_conf { > + my $rpcenv = eval { PVE::RPCEnvironment::get(); }; Missing use statement for that module, also the semicolon inside the eval can be dropped. > + > + if ($@) { > + warn "could not load SDN configuration because RPCEnvironment is not > initialized."; > + return {}; > + } > + > + my $authuser = $rpcenv->get_user(); > + > + my $guests = PVE::Cluster::get_vmlist(); > + my $allowed_vms = []; > + foreach my $vmid (sort keys %{$guests->{ids}}) { > + next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); > + push @$allowed_vms, $vmid; > + } Above one could be: $allowed_vms = [ grep { $rpcenv->check($authuser, "/vms/$_", [ 'VM.Audit' ], 1) } sort keys $guests->{ids}->%* ]; > + my $vnets = PVE::Network::SDN::Vnets::config(1); > + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; > + my $allowed_vnets = []; > + foreach my $vnet (sort keys %{$vnets->{ids}}) { > + my $zone = $vnets->{ids}->{$vnet}->{zone}; > + next if !$rpcenv->check_any($authuser, "/sdn/zones/$zone/$vnet", > $privs, 1); > + push @$allowed_vnets, $vnet; > + } > + > + my $sdn_config = { > + ipset => {} , > + ipset_comments => {}, > + }; > + > + eval { > + $sdn_config = PVE::RS::Firewall::SDN::config($allowed_vnets, > $allowed_vms); > + }; > + warn $@ if $@; above might be more readable by doing: my $empty_sdn_config = { ipset => {} , ipset_comments => {} }; my $sdn_config = eval { PVE::RS::Firewall::SDN::config($allowed_vnets, $allowed_vms) }; warn $@ if $@; return $sdn_config // $empty_sdn_config; > + > + return $sdn_config; > +} > + > sub save_clusterfw_conf { > my ($cluster_conf) = @_; > > @@ -3768,7 +3815,7 @@ sub compile { > > $vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, > $testdir); > } else { # normal operation > - $cluster_conf = load_clusterfw_conf(undef) if !$cluster_conf; > + $cluster_conf = load_clusterfw_conf() if !$cluster_conf; > > $hostfw_conf = load_hostfw_conf($cluster_conf, undef) if !$hostfw_conf; > > @@ -4043,6 +4090,7 @@ sub compile_ipsets { > } > > generate_ipset_chains($ipset_ruleset, undef, $cluster_conf, undef, > $cluster_conf->{ipset}); > + generate_ipset_chains($ipset_ruleset, undef, $cluster_conf, undef, > $cluster_conf->{sdn}->{ipset}); > > return $ipset_ruleset; > } > diff --git a/src/PVE/Service/pve_firewall.pm b/src/PVE/Service/pve_firewall.pm > index 65cb2b8..02b507a 100755 > --- a/src/PVE/Service/pve_firewall.pm > +++ b/src/PVE/Service/pve_firewall.pm > @@ -158,7 +158,7 @@ __PACKAGE__->register_method ({ > > PVE::Firewall::set_verbose(1); # show syntax errors > > - my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef); > + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); just to be sure, above and below does not change anything, or? > $res->{enable} = $cluster_conf->{options}->{enable} ? 1 : 0; > > if ($status eq 'running') { > @@ -202,7 +202,7 @@ __PACKAGE__->register_method ({ > > PVE::Firewall::set_verbose(1); > > - my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef); > + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); > my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = > PVE::Firewall::compile($cluster_conf, undef, undef); > > print "ipset cmdlist:\n"; _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel