]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
skip group rules generation if rule ipversion don't match iptables version
[pve-firewall.git] / src / PVE / Firewall.pm
index 727204a733afd20fe7780456495ecbfd0f388a60..3d4a36528d78c6b1cdaf76072218ac03f2718896 100644 (file)
@@ -489,7 +489,8 @@ my $pve_fw_parsed_macros;
 my $pve_fw_macro_descr;
 my $pve_fw_preferred_macro_names = {};
 
-my $pve_std_chains = {
+my $pve_std_chains = {};
+$pve_std_chains->{4} = {
     'PVEFW-SET-ACCEPT-MARK' => [
        "-j MARK --set-mark 1",
     ],
@@ -760,7 +761,10 @@ sub local_network {
     return $__local_network;
 }
 
-my $max_iptables_ipset_name_length = 27;
+# ipset names are limited to 31 characters, and we use '_swap' 
+# suffix for atomic update, for example PVEFW-${VMID}-${ipset_name}_swap
+
+my $max_iptables_ipset_name_length = 31 - length("_swap");
 
 sub compute_ipset_chain_name {
     my ($vmid, $ipset_name) = @_;
@@ -798,16 +802,28 @@ sub parse_address_list {
 
     my $count = 0;
     my $iprange = 0;
+    my $ipversion;
+
     foreach my $elem (split(/,/, $str)) {
        $count++;
-       if (!Net::IP->new($elem)) {
+       my $ip = Net::IP->new($elem);
+       if (!$ip) {
            my $err = Net::IP::Error();
            die "invalid IP address: $err\n";
        }
        $iprange = 1 if $elem =~ m/-/;
+
+       my $new_ipversion = Net::IP::ip_is_ipv6($ip->ip()) ? 6 : 4;
+
+       die "detected mixed ipv4/ipv6 addresses in address list '$str'\n"
+           if $ipversion && ($new_ipversion != $ipversion);
+
+       $ipversion = $new_ipversion;
     }
 
-    die "you can use a range in a list\n" if $iprange && $count > 1;
+    die "you can't use a range in a list\n" if $iprange && $count > 1;
+
+    return $ipversion;
 }
 
 sub parse_port_name_number_or_range {
@@ -849,8 +865,8 @@ sub pve_fw_verify_port_spec {
    return $portstr;
 }
 
-PVE::JSONSchema::register_format('pve-fw-v4addr-spec', \&pve_fw_verify_v4addr_spec);
-sub pve_fw_verify_v4addr_spec {
+PVE::JSONSchema::register_format('pve-fw-addr-spec', \&pve_fw_verify_addr_spec);
+sub pve_fw_verify_addr_spec {
    my ($list) = @_;
 
    parse_address_list($list);
@@ -949,11 +965,11 @@ my $rule_properties = {
     },
     iface => get_standard_option('pve-iface', { optional => 1 }),
     source => {
-       type => 'string', format => 'pve-fw-v4addr-spec',
+       type => 'string', format => 'pve-fw-addr-spec',
        optional => 1,
     },
     dest => {
-       type => 'string', format => 'pve-fw-v4addr-spec',
+       type => 'string', format => 'pve-fw-addr-spec',
        optional => 1,
     },
     proto => {
@@ -1164,14 +1180,21 @@ sub verify_rule {
            if !$rule->{proto};
     }
 
+    my $ipversion;
+
     if ($rule->{source}) {
-       eval { parse_address_list($rule->{source}); };
+       eval { $ipversion = parse_address_list($rule->{source}); };
        &$add_error('source', $@) if $@;
        &$check_ipset_or_alias_property('source');
     }
 
     if ($rule->{dest}) {
-       eval { parse_address_list($rule->{dest}); };
+       eval { 
+           my $dest_ipversion = parse_address_list($rule->{dest}); 
+           die "detected mixed ipv4/ipv6 adresses in rule\n"
+               if defined($ipversion) && ($dest_ipversion != $ipversion);
+           $ipversion = $dest_ipversion;
+       };
        &$add_error('dest', $@) if $@;
        &$check_ipset_or_alias_property('dest');
     }
@@ -1191,6 +1214,7 @@ sub verify_rule {
     }
 
     $rule->{errors} = $errors if $error_count;
+    $rule->{ipversion} = $ipversion if $ipversion;
 
     return $rule;
 }
@@ -1697,12 +1721,12 @@ sub ruleset_create_vm_chain {
 }
 
 sub ruleset_add_group_rule {
-    my ($ruleset, $cluster_conf, $chain, $rule, $direction, $action) = @_;
+    my ($ruleset, $cluster_conf, $chain, $rule, $direction, $action, $ipversion) = @_;
 
     my $group = $rule->{action};
     my $group_chain = "GROUP-$group-$direction";
     if(!ruleset_chain_exist($ruleset, $group_chain)){
-       generate_group_rules($ruleset, $cluster_conf, $group);
+       generate_group_rules($ruleset, $cluster_conf, $group, $ipversion);
     }
 
     if ($direction eq 'OUT' && $rule->{iface_out}) {
@@ -1717,7 +1741,7 @@ sub ruleset_add_group_rule {
 }
 
 sub ruleset_generate_vm_rules {
-    my ($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, $netid, $direction, $options) = @_;
+    my ($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, $netid, $direction, $options, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1726,9 +1750,11 @@ sub ruleset_generate_vm_rules {
     foreach my $rule (@$rules) {
        next if $rule->{iface} && $rule->{iface} ne $netid;
        next if !$rule->{enable} || $rule->{errors};
+       next if $rule->{ipversion} && ($rule->{ipversion} != $ipversion);
+
        if ($rule->{type} eq 'group') {
            ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, $direction,
-                                  $direction eq 'OUT' ? 'RETURN' : $in_accept);
+                                  $direction eq 'OUT' ? 'RETURN' : $in_accept, $ipversion);
        } else {
            next if $rule->{type} ne $lc_direction;
            eval {
@@ -1781,7 +1807,7 @@ sub ruleset_generate_vm_ipsrules {
 }
 
 sub generate_venet_rules_direction {
-    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
+    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1794,7 +1820,7 @@ sub generate_venet_rules_direction {
 
     ruleset_create_vm_chain($ruleset, $chain, $options, undef, undef, $direction);
 
-    ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction);
+    ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction, undef, $ipversion);
 
     # implement policy
     my $policy;
@@ -1823,7 +1849,7 @@ sub generate_venet_rules_direction {
 }
 
 sub generate_tap_rules_direction {
-    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $direction) = @_;
+    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $direction, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1842,7 +1868,7 @@ sub generate_tap_rules_direction {
     ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $ipfilter_ipset, $direction);
 
     if ($options->{enable}) {
-       ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options);
+       ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options, $ipversion);
 
        ruleset_generate_vm_ipsrules($ruleset, $options, $direction, $iface);
 
@@ -1874,7 +1900,7 @@ sub generate_tap_rules_direction {
 }
 
 sub enable_host_firewall {
-    my ($ruleset, $hostfw_conf, $cluster_conf) = @_;
+    my ($ruleset, $hostfw_conf, $cluster_conf, $ipversion) = @_;
 
     my $options = $hostfw_conf->{options};
     my $cluster_options = $cluster_conf->{options};
@@ -1905,7 +1931,7 @@ sub enable_host_firewall {
 
        eval {
            if ($rule->{type} eq 'group') {
-               ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action);
+               ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action, $ipversion);
            } elsif ($rule->{type} eq 'in') {
                ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
                                      undef, $cluster_conf, $hostfw_conf);
@@ -1958,7 +1984,7 @@ sub enable_host_firewall {
        $rule->{iface_out} = $rule->{iface} if $rule->{iface};
        eval {
            if ($rule->{type} eq 'group') {
-               ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'OUT', $accept_action);
+               ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'OUT', $accept_action, $ipversion);
            } elsif ($rule->{type} eq 'out') {
                ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
                                      undef, $cluster_conf, $hostfw_conf);
@@ -1989,7 +2015,7 @@ sub enable_host_firewall {
 }
 
 sub generate_group_rules {
-    my ($ruleset, $cluster_conf, $group) = @_;
+    my ($ruleset, $cluster_conf, $group, $ipversion) = @_;
 
     my $rules = $cluster_conf->{groups}->{$group};
 
@@ -2005,6 +2031,7 @@ sub generate_group_rules {
 
     foreach my $rule (@$rules) {
        next if $rule->{type} ne 'in';
+       next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion;
        ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, undef, $cluster_conf);
     }
 
@@ -2015,6 +2042,7 @@ sub generate_group_rules {
 
     foreach my $rule (@$rules) {
        next if $rule->{type} ne 'out';
+       next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion;
        # we use PVEFW-SET-ACCEPT-MARK (Instead of ACCEPT) because we need to
        # check also other tap rules later
        ruleset_generate_rule($ruleset, $chain, $rule,
@@ -2638,29 +2666,35 @@ sub get_option_log_level {
 }
 
 sub generate_std_chains {
-    my ($ruleset, $options) = @_;
+    my ($ruleset, $options, $ipversion) = @_;
+
+    my $std_chains = $pve_std_chains->{$ipversion} || die "internal error";
 
     my $loglevel = get_option_log_level($options, 'smurf_log_level');
 
-    # same as shorewall smurflog.
-    my $chain = 'PVEFW-smurflog';
-    $pve_std_chains->{$chain} = [];
+    my $chain;
 
-    push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
-    push @{$pve_std_chains->{$chain}}, "-j DROP";
+    if ($ipversion == 4) {
+       # same as shorewall smurflog.
+       $chain = 'PVEFW-smurflog';
+       $std_chains->{$chain} = [];
+       
+       push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
+       push @{$std_chains->{$chain}}, "-j DROP";
+    }
 
     # same as shorewall logflags action.
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     $chain = 'PVEFW-logflags';
-    $pve_std_chains->{$chain} = [];
+    $std_chains->{$chain} = [];
 
     # fixme: is this correctly logged by pvewf-logger? (ther is no --log-ip-options for NFLOG)
-    push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
-    push @{$pve_std_chains->{$chain}}, "-j DROP";
+    push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
+    push @{$std_chains->{$chain}}, "-j DROP";
 
-    foreach my $chain (keys %$pve_std_chains) {
+    foreach my $chain (keys %$std_chains) {
        ruleset_create_chain($ruleset, $chain);
-       foreach my $rule (@{$pve_std_chains->{$chain}}) {
+       foreach my $rule (@{$std_chains->{$chain}}) {
            if (ref($rule)) {
                ruleset_generate_rule($ruleset, $chain, $rule);
            } else {
@@ -2833,6 +2867,13 @@ sub compile {
        $vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, undef, $verbose);
     }
 
+    my ($ruleset, $ipset_ruleset) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose);
+    return ($ruleset, $ipset_ruleset);
+}
+
+sub compile_iptables_filter {
+    my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_;
+
     $cluster_conf->{ipset}->{venet0} = [];
     my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0');
 
@@ -2880,14 +2921,15 @@ sub compile {
 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN");
 
-    generate_std_chains($ruleset, $hostfw_options);
+    generate_std_chains($ruleset, $hostfw_options, $ipversion);
 
     my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0));
 
     my $ipset_ruleset = {};
 
-    if ($hostfw_enable) {
-       eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf); };
+    # currently pveproxy don't works with ipv6, so let's generate host fw ipv4 only for the moment
+    if ($hostfw_enable && ($ipversion == 4)) {
+       eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf, $ipversion); };
        warn $@ if $@; # just to be sure - should not happen
     }
 
@@ -2910,9 +2952,9 @@ sub compile {
 
                my $macaddr = $net->{macaddr};
                generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                            $vmfw_conf, $vmid, 'IN');
+                                            $vmfw_conf, $vmid, 'IN', $ipversion);
                generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                            $vmfw_conf, $vmid, 'OUT');
+                                            $vmfw_conf, $vmid, 'OUT', $ipversion);
            }
        };
        warn $@ if $@; # just to be sure - should not happen
@@ -2932,18 +2974,20 @@ sub compile {
                if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
                    my $ip = $conf->{ip_address}->{value};
                    $ip =~ s/\s+/,/g;
-                   parse_address_list($ip); # make sure we have a valid $ip list
 
-                   my @ips = split(',', $ip);
+                   my @ips = ();
 
-                   foreach my $singleip (@ips) {
-                       my $venet0ipset = {};
-                       $venet0ipset->{cidr} = $singleip;
-                       push @{$cluster_conf->{ipset}->{venet0}}, $venet0ipset;
+                   foreach my $singleip (split(',', $ip)) {
+                       my $singleip_ver = parse_address_list($singleip); # make sure we have a valid $ip list
+                       push @{$cluster_conf->{ipset}->{venet0}}, { cidr => $singleip };
+                       push @ips, $singleip if $singleip_ver == $ipversion;
                    }
 
-                   generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN');
-                   generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT');
+                   if (scalar(@ips)) {
+                       my $ip_list = join(',', @ips);
+                       generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'IN', $ipversion);
+                       generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'OUT', $ipversion);
+                   }
                }
            }
 
@@ -2956,9 +3000,9 @@ sub compile {
                    my $macaddr = $d->{mac};
                    my $iface = $d->{host_ifname};
                    generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                                $vmfw_conf, $vmid, 'IN');
+                                                $vmfw_conf, $vmid, 'IN', $ipversion);
                    generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                                $vmfw_conf, $vmid, 'OUT');
+                                                $vmfw_conf, $vmid, 'OUT', $ipversion);
                }
            }
        };