]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
Add ipv6 macros to the macro list
[pve-firewall.git] / src / PVE / Firewall.pm
index 958927acdf70bbf906f49b29f4af1fbcb9a62e12..37538c34ea9d2df4bc02e24ccb095dd44821fd05 100644 (file)
@@ -113,8 +113,6 @@ eval  {
 
 };
 
-use Data::Dumper;
-
 my $nodename = PVE::INotify::nodename();
 
 my $pve_fw_lock_filename = "/var/lock/pvefw.lck";
@@ -132,6 +130,17 @@ my $log_level_hash = {
     emerg => 0,
 };
 
+# we need to overwrite some macros for ipv6
+my $pve_ipv6fw_macros = {
+    'Ping' => [
+       { action => 'PARAM', proto => 'icmpv6', dport => 'echo-request' },
+    ],
+    'Trcrt' => [
+       { action => 'PARAM', proto => 'udp', dport => '33434:33524' },
+       { action => 'PARAM', proto => 'icmpv6', dport => 'echo-request' },
+    ],
+ };
+
 # imported/converted from: /usr/share/shorewall/macro.*
 my $pve_fw_macros = {
     'Amanda' => [
@@ -157,6 +166,11 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'tcp', dport => '6881:6999' },
        { action => 'PARAM', proto => 'udp', dport => '6881' },
     ],
+    'Ceph' => [
+        "Ceph Storage Cluster traffic (Ceph Monitors, OSD & MDS Deamons)",
+        { action => 'PARAM', proto => 'tcp', dport => '6789' },
+        { action => 'PARAM', proto => 'tcp', dport => '6800:7300' },
+    ],
     'CVS' => [
        "Concurrent Versions System pserver traffic",
        { action => 'PARAM', proto => 'tcp', dport => '2401' },
@@ -486,6 +500,7 @@ my $pve_fw_macros = {
 
 my $pve_fw_parsed_macros;
 my $pve_fw_macro_descr;
+my $pve_fw_macro_ipversion = {};
 my $pve_fw_preferred_macro_names = {};
 
 my $pve_std_chains = {};
@@ -724,7 +739,9 @@ my $icmpv6_type_names = {
     'echo-reply' => 1,
     'router-solicitation' => 1,
     'router-advertisement' => 1,
+    'neighbor-solicitation' => 1,
     'neighbour-solicitation' => 1,
+    'neighbor-advertisement' => 1,
     'neighbour-advertisement' => 1,
     'redirect' => 1,
 };
@@ -733,14 +750,32 @@ sub init_firewall_macros {
 
     $pve_fw_parsed_macros = {};
 
-    foreach my $k (keys %$pve_fw_macros) {
+    my $parse = sub {
+       my ($k, $macro) = @_;
        my $lc_name = lc($k);
-       my $macro = $pve_fw_macros->{$k};
-       if (!ref($macro->[0])) {
-           $pve_fw_macro_descr->{$k} = shift @$macro;
+       $pve_fw_macro_ipversion->{$k} = 0;
+       while (!ref($macro->[0])) {
+           my $desc = shift @$macro;
+           if ($desc eq 'ipv4only') {
+               $pve_fw_macro_ipversion->{$k} = 4;
+           } elsif ($desc eq 'ipv6only') {
+               $pve_fw_macro_ipversion->{$k} = 6;
+           } else {
+               $pve_fw_macro_descr->{$k} = $desc;
+           }
        }
        $pve_fw_preferred_macro_names->{$lc_name} = $k;
        $pve_fw_parsed_macros->{$k} = $macro;
+    };
+
+    foreach my $k (keys %$pve_fw_macros) {
+       &$parse($k, $pve_fw_macros->{$k});
+    }
+
+    foreach my $k (keys %$pve_ipv6fw_macros) {
+       next if $pve_fw_parsed_macros->{$k};
+       &$parse($k, $pve_ipv6fw_macros->{$k});
+       $pve_fw_macro_ipversion->{$k} = 6;
     }
 }
 
@@ -1095,7 +1130,8 @@ my $rule_properties = {
        optional => 1,
     },
     enable => {
-       type => 'boolean',
+        type => 'integer',
+       minimum => 0,
        optional => 1,
     },
     sport => {
@@ -1139,11 +1175,18 @@ sub delete_rule_properties {
 }
 
 my $apply_macro = sub {
-    my ($macro_name, $param, $verify) = @_;
+    my ($macro_name, $param, $verify, $ipversion) = @_;
 
     my $macro_rules = $pve_fw_parsed_macros->{$macro_name};
     die "unknown macro '$macro_name'\n" if !$macro_rules; # should not happen
 
+    if ($ipversion && ($ipversion == 6) && $pve_ipv6fw_macros->{$macro_name}) {
+       $macro_rules = $pve_ipv6fw_macros->{$macro_name};
+    }
+
+    # skip macros which are specific to another ipversion
+    return if ($ipversion//0) != ($pve_fw_macro_ipversion->{$macro_name}//0);
+
     my $rules = [];
 
     foreach my $templ (@$macro_rules) {
@@ -1243,8 +1286,7 @@ sub verify_rule {
                my $alias = lc($value);
                &$add_error($name, "no such alias '$value'")
                    if !($cluster_conf->{aliases}->{$alias} || ($fw_conf && $fw_conf->{aliases}->{$alias}));
-
-               my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+               my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
                $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
 
                &$set_ip_version($e->{ipversion});
@@ -1333,8 +1375,10 @@ sub verify_rule {
        &$check_ipset_or_alias_property('dest', $ipversion);
     }
 
+    $rule->{ipversion} = $ipversion if $ipversion;
+
     if ($rule->{macro} && !$error_count) {
-       eval { &$apply_macro($rule->{macro}, $rule, 1); };
+       eval { &$apply_macro($rule->{macro}, $rule, 1, $ipversion); };
        if (my $err = $@) {
            if (ref($err) eq "PVE::Exception" && $err->{errors}) {
                my $eh = $err->{errors};
@@ -1348,7 +1392,6 @@ sub verify_rule {
     }
 
     $rule->{errors} = $errors if $error_count;
-    $rule->{ipversion} = $ipversion if $ipversion;
 
     return $rule;
 }
@@ -1442,7 +1485,7 @@ sub ip6tables_restore_cmdlist {
 sub ipset_restore_cmdlist {
     my ($cmdlist) = @_;
 
-    run_command("/usr/sbin/ipset restore", input => $cmdlist, errmsg => "ipset_restore_cmdlist");
+    run_command("/sbin/ipset restore", input => $cmdlist, errmsg => "ipset_restore_cmdlist");
 }
 
 sub iptables_get_chains {
@@ -1547,7 +1590,7 @@ sub ipset_get_chains {
        }
     };
 
-    run_command("/usr/sbin/ipset save", outfunc => $parser);
+    run_command("/sbin/ipset save", outfunc => $parser);
 
     # compute digest for each chain
     foreach my $chain (keys %$chains) {
@@ -1594,7 +1637,7 @@ sub ruleset_generate_cmdstr {
            }
        } elsif ($source =~ m/^${ip_alias_pattern}$/){
            my $alias = lc($source);
-           my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+           my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
            $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
            die "no such alias '$source'\n" if !$e;
            push @cmd, "-s $e->{cidr}";
@@ -1623,7 +1666,7 @@ sub ruleset_generate_cmdstr {
            }
        } elsif ($dest =~ m/^${ip_alias_pattern}$/){
            my $alias = lc($dest);
-           my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+           my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
            $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
            die "no such alias '$dest'\n" if !$e;
            push @cmd, "-d $e->{cidr}";
@@ -1697,7 +1740,7 @@ sub ruleset_generate_rule {
     my $rules;
 
     if ($rule->{macro}) {
-       $rules = &$apply_macro($rule->{macro}, $rule);
+       $rules = &$apply_macro($rule->{macro}, $rule, 0, $ipversion);
     } else {
        $rules = [ $rule ];
     }
@@ -2077,6 +2120,7 @@ sub enable_host_firewall {
     # add host rules first, so that cluster wide rules can be overwritten
     foreach my $rule (@$rules, @$cluster_rules) {
        next if !$rule->{enable} || $rule->{errors};
+       next if $rule->{ipversion} && ($rule->{ipversion} != $ipversion);
 
        $rule->{iface_in} = $rule->{iface} if $rule->{iface};
 
@@ -2101,10 +2145,11 @@ sub enable_host_firewall {
     ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 3128 -j $accept_action");  # SPICE Proxy
     ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 22 -j $accept_action");  # SSH
 
-    my $localnet = local_network();
+    my $localnet = $cluster_conf->{aliases}->{local_network}->{cidr};
+    my $localnet_ver = $cluster_conf->{aliases}->{local_network}->{ipversion};
 
     # corosync
-    if ($localnet) {
+    if ($localnet && ($ipversion == $localnet_ver)) {
        my $corosync_rule = "-p udp --dport 5404:5405 -j $accept_action";
        ruleset_addrule($ruleset, $chain, "-s $localnet -d $localnet $corosync_rule");
        ruleset_addrule($ruleset, $chain, "-s $localnet -m addrtype --dst-type MULTICAST $corosync_rule");
@@ -2132,6 +2177,7 @@ sub enable_host_firewall {
     # add host rules first, so that cluster wide rules can be overwritten
     foreach my $rule (@$rules, @$cluster_rules) {
        next if !$rule->{enable} || $rule->{errors};
+       next if $rule->{ipversion} && ($rule->{ipversion} != $ipversion);
 
        $rule->{iface_out} = $rule->{iface} if $rule->{iface};
        eval {
@@ -2148,7 +2194,7 @@ sub enable_host_firewall {
     }
 
     # allow standard traffic on cluster network
-    if ($localnet) {
+    if ($localnet && ($ipversion == $localnet_ver)) {
        ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 8006 -j $accept_action");  # PVE API
        ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 22 -j $accept_action");  # SSH
        ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 5900:5999 -j $accept_action");  # PVE VNC Console
@@ -2342,9 +2388,12 @@ sub parse_clusterfw_option {
 
     my ($opt, $value);
 
-    if ($line =~ m/^(enable):\s*(0|1)\s*$/i) {
+    if ($line =~ m/^(enable):\s*(\d+)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
+       if (($value > 1) && ((time() - $value) > 60)) {
+           $value = 0
+       }
     } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) {
        $opt = lc($1);
        $value = uc($3);
@@ -2359,7 +2408,7 @@ sub resolve_alias {
     my ($clusterfw_conf, $fw_conf, $cidr) = @_;
 
     my $alias = lc($cidr);
-    my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+    my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
     $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf;
 
     die "no such alias '$cidr'\n" if !$e;;
@@ -3072,8 +3121,11 @@ sub compile_iptables_filter {
     if ($cluster_conf->{aliases}->{local_network}) {
        $localnet = $cluster_conf->{aliases}->{local_network}->{cidr};
     } else {
-       $localnet = local_network() || '127.0.0.0/8';
-       $cluster_conf->{aliases}->{local_network} = { cidr => $localnet };
+       my $localnet_ver;
+       ($localnet, $localnet_ver) = parse_ip_or_cidr(local_network() || '127.0.0.0/8');
+
+       $cluster_conf->{aliases}->{local_network} = { 
+           name => 'local_network', cidr => $localnet, ipversion => $localnet_ver };
     }
 
     push @{$cluster_conf->{ipset}->{management}}, { cidr => $localnet };
@@ -3118,8 +3170,7 @@ sub compile_iptables_filter {
 
     my $ipset_ruleset = {};
 
-    # currently pveproxy don't works with ipv6, so let's generate host fw ipv4 only for the moment
-    if ($hostfw_enable && ($ipversion == 4)) {
+    if ($hostfw_enable) {
        eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf, $ipversion); };
        warn $@ if $@; # just to be sure - should not happen
     }