]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
Revert "rules: verify referenced security group exists"
[pve-firewall.git] / src / PVE / Firewall.pm
index d22b15a506387bd7462ee8ae8ffa0f60f3ddf96e..a2105e5410590b30305bd6941ddcc8bfe40159da 100644 (file)
@@ -785,12 +785,14 @@ my $icmp_type_names = {
 # ip6tables -p icmpv6 -h
 
 my $icmpv6_type_names = {
-    'any' => 1,
     'destination-unreachable' => 1,
     'no-route' => 1,
     'communication-prohibited' => 1,
+    'beyond-scope' => 1,
     'address-unreachable' => 1,
     'port-unreachable' => 1,
+    'failed-policy' => 1,
+    'reject-route' => 1,
     'packet-too-big' => 1,
     'time-exceeded' => 1,
     'ttl-zero-during-transit' => 1,
@@ -810,6 +812,17 @@ my $icmpv6_type_names = {
     'redirect' => 1,
 };
 
+my $is_valid_icmp_type = sub {
+    my ($type, $valid_types) = @_;
+
+    if ($type =~ m/^\d+$/) {
+       # values for icmp-type range between 0 and 255 (8 bit field)
+       die "invalid icmp-type '$type'\n" if $type > 255;
+    } else {
+       die "unknown icmp-type '$type'\n" if !defined($valid_types->{$type});
+    }
+};
+
 sub init_firewall_macros {
 
     $pve_fw_parsed_macros = {};
@@ -1080,7 +1093,7 @@ sub parse_port_name_number_or_range {
        }
     }
 
-    die "ICPM ports not allowed in port range\n" if $icmp_port && $count > 0;
+    die "ICMP ports not allowed in port range\n" if $icmp_port && $count > 0;
 
     # I really don't like to use the word number here, but it's the only thing
     # that makes sense in a literal way. The range 1:100 counts as 2, not as
@@ -2035,23 +2048,21 @@ sub ipt_rule_to_cmds {
            my $multisport = defined($rule->{sport}) && parse_port_name_number_or_range($rule->{sport}, 0);
 
            my $add_dport = sub {
-               return if !$rule->{dport};
+               return if !defined($rule->{dport});
 
+               # NOTE: we re-use dport to store --icmp-type for icmp* protocol
                if ($proto eq 'icmp') {
-                   # Note: we use dport to store --icmp-type
-                   die "unknown icmp-type '$rule->{dport}'\n"
-                       if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}});
+                   $is_valid_icmp_type->($rule->{dport}, $icmp_type_names);
                    push @match, "-m icmp --icmp-type $rule->{dport}";
                } elsif ($proto eq 'icmpv6') {
-                   # Note: we use dport to store --icmpv6-type
-                   die "unknown icmpv6-type '$rule->{dport}'\n"
-                       if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}});
+                   $is_valid_icmp_type->($rule->{dport}, $icmpv6_type_names);
                    push @match, "-m icmpv6 --icmpv6-type $rule->{dport}";
                } elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) {
                    die "protocol $proto does not have ports\n";
                } elsif ($multidport) {
                    push @match, "--match multiport", "--dports $rule->{dport}";
                } else {
+                   return if !$rule->{dport};
                    push @match, "--dport $rule->{dport}";
                }
            };
@@ -2897,6 +2908,8 @@ sub generic_fw_config_parser {
     }
     return {} if !$raw;
 
+    my $curr_group_keys = {};
+
     my $linenr = 0;
     while ($raw =~ /^\h*(.*?)\h*$/gm) {
        my $line = $1;
@@ -2957,6 +2970,8 @@ sub generic_fw_config_parser {
            }
 
            $res->{$section}->{$group} = [];
+           $curr_group_keys = {};
+
            $res->{ipset_comments}->{$group} = decode('utf8', $comment)
                if $comment;
            next;
@@ -3021,6 +3036,8 @@ sub generic_fw_config_parser {
                } else {
                    $cidr = parse_ip_or_cidr($cidr);
                }
+               die "duplicate ipset entry for '$cidr'\n"
+                   if defined($curr_group_keys->{$cidr});
            };
            if (my $err = $@) {
                chomp $err;
@@ -3044,6 +3061,7 @@ sub generic_fw_config_parser {
            }
 
            push @{$res->{$section}->{$group}}, $entry;
+           $curr_group_keys->{$cidr} = 1;
        } else {
            warn "$prefix: skip line - unknown section\n";
            next;
@@ -3053,6 +3071,8 @@ sub generic_fw_config_parser {
     return $res;
 }
 
+# this is only used to prevent concurrent runs of rule compilation/application
+# see lock_*_conf for cfs locks protectiong config modification
 sub run_locked {
     my ($code, @param) = @_;
 
@@ -3101,6 +3121,18 @@ sub read_local_vm_config {
     return $vmdata;
 };
 
+sub lock_vmfw_conf {
+    my ($vmid, $timeout, $code, @param) = @_;
+
+    die "can't lock VM firewall config for undefined VMID\n"
+       if !defined($vmid);
+
+    my $res = PVE::Cluster::cfs_lock_firewall("vm-$vmid", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_vmfw_conf {
     my ($cluster_conf, $rule_env, $vmid, $dir) = @_;
 
@@ -3207,7 +3239,13 @@ my $format_ipsets = sub {
 
        my $nethash = {};
        foreach my $entry (@$options) {
-           $nethash->{$entry->{cidr}} = $entry;
+           my $cidr = $entry->{cidr};
+           if (defined($nethash->{$cidr})) {
+               warn "ignoring duplicate ipset entry '$cidr'\n";
+               next;
+           }
+
+           $nethash->{$cidr} = $entry;
        }
 
        foreach my $cidr (sort keys %$nethash) {
@@ -3268,13 +3306,15 @@ sub clone_vmfw_conf {
     my $sourcevm_conffile = "$pvefw_conf_dir/$vmid.fw";
     my $clonevm_conffile = "$pvefw_conf_dir/$newid.fw";
 
-    if (-f $clonevm_conffile) {
-       unlink $clonevm_conffile;
-    }
-    if (-f $sourcevm_conffile) {
-       my $data = PVE::Tools::file_get_contents($sourcevm_conffile);
-       PVE::Tools::file_set_contents($clonevm_conffile, $data);
-    }
+    lock_vmfw_conf($newid, 10, sub {
+       if (-f $clonevm_conffile) {
+           unlink $clonevm_conffile;
+       }
+       if (-f $sourcevm_conffile) {
+           my $data = PVE::Tools::file_get_contents($sourcevm_conffile);
+           PVE::Tools::file_set_contents($clonevm_conffile, $data);
+       }
+    });
 }
 
 sub read_vm_firewall_configs {
@@ -3448,6 +3488,15 @@ my $set_global_log_ratelimit = sub {
     }
 };
 
+sub lock_clusterfw_conf {
+    my ($timeout, $code, @param) = @_;
+
+    my $res = PVE::Cluster::cfs_lock_firewall("cluster", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_clusterfw_conf {
     my ($filename) = @_;
 
@@ -3511,6 +3560,15 @@ sub save_clusterfw_conf {
     }
 }
 
+sub lock_hostfw_conf {
+    my ($timeout, $code, @param) = @_;
+
+    my $res = PVE::Cluster::cfs_lock_firewall("host-$nodename", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_hostfw_conf {
     my ($cluster_conf, $filename) = @_;
 
@@ -3904,7 +3962,9 @@ sub compile_ebtables_filter {
                    # ebtables changes this to a .0/MASK network but we just
                    # want the address here, no network - see #2193
                    $ip =~ s|/(\d+)$||;
-                   push @$arpfilter, $ip;
+                   if ($ip ne 'dhcp') {
+                       push @$arpfilter, $ip;
+                   }
                }
                generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid, $arpfilter);
            }