]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
get_ipset_cmdlist: avoid restore problems due to wrong order
[pve-firewall.git] / src / PVE / Firewall.pm
index 9e85652a57ee34c3682c59b72cee2a93f9084a2b..4935be854957e974f2ee20d30d1412295c6b5513 100644 (file)
@@ -572,6 +572,92 @@ $pve_std_chains->{4} = {
     ],
 };
 
+$pve_std_chains->{6} = {
+    'PVEFW-SET-ACCEPT-MARK' => [
+        "-j MARK --set-mark 1",
+    ],
+    'PVEFW-DropBroadcast' => [
+        # same as shorewall 'Broadcast'
+        # simply DROP BROADCAST/MULTICAST/ANYCAST
+        # we can use this to reduce logging
+        #{ action => 'DROP', dsttype => 'BROADCAST' }, #no broadcast in ipv6
+       # ipv6 addrtype does not work with kernel 2.6.32
+       #{ action => 'DROP', dsttype => 'MULTICAST' },
+        #{ action => 'DROP', dsttype => 'ANYCAST' },
+        { action => 'DROP', dest => 'ff00::/8' },
+        #{ action => 'DROP', dest => '224.0.0.0/4' },
+    ],
+    'PVEFW-reject' => [
+        # same as shorewall 'reject'
+        #{ action => 'DROP', dsttype => 'BROADCAST' },
+        #{ action => 'DROP', source => '224.0.0.0/4' },
+       { action => 'DROP', proto => 'icmpv6' },
+        "-p tcp -j REJECT --reject-with tcp-reset",
+        #"-p udp -j REJECT --reject-with icmp-port-unreachable",
+        #"-p icmp -j REJECT --reject-with icmp-host-unreachable",
+        #"-j REJECT --reject-with icmp-host-prohibited",
+    ],
+    'PVEFW-Drop' => [
+        # same as shorewall 'Drop', which is equal to DROP,
+        # but REJECT/DROP some packages to reduce logging,
+        # and ACCEPT critical ICMP types
+       { action => 'PVEFW-reject', proto => 'tcp', dport => '43' }, # REJECT 'auth'
+        # we are not interested in BROADCAST/MULTICAST/ANYCAST
+        { action => 'PVEFW-DropBroadcast' },
+        # ACCEPT critical ICMP types
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'destination-unreachable' },
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'time-exceeded' },
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'packet-too-big' },
+
+        # Drop packets with INVALID state
+        "-m conntrack --ctstate INVALID -j DROP",
+        # Drop Microsoft SMB noise
+       { action => 'DROP', proto => 'udp', dport => '135,445', nbdport => 2 },
+       { action => 'DROP', proto => 'udp', dport => '137:139'},
+       { action => 'DROP', proto => 'udp', dport => '1024:65535', sport => 137 },
+       { action => 'DROP', proto => 'tcp', dport => '135,139,445', nbdport => 3 },
+       { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP
+        # Drop new/NotSyn traffic so that it doesn't get logged
+        "-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j DROP",
+        # Drop DNS replies
+       { action => 'DROP', proto => 'udp', sport => 53 },
+    ],
+    'PVEFW-Reject' => [
+        # same as shorewall 'Reject', which is equal to Reject,
+        # but REJECT/DROP some packages to reduce logging,
+        # and ACCEPT critical ICMP types
+        { action => 'PVEFW-reject',  proto => 'tcp', dport => '43' }, # REJECT 'auth'
+        # we are not interested in BROADCAST/MULTICAST/ANYCAST
+        { action => 'PVEFW-DropBroadcast' },
+        # ACCEPT critical ICMP types
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'destination-unreachable' },
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'time-exceeded' },
+        { action => 'ACCEPT', proto => 'icmpv6', dport => 'packet-too-big' },
+
+        # Drop packets with INVALID state
+        "-m conntrack --ctstate INVALID -j DROP",
+        # Drop Microsoft SMB noise
+        { action => 'PVEFW-reject', proto => 'udp', dport => '135,445', nbdport => 2 },
+        { action => 'PVEFW-reject', proto => 'udp', dport => '137:139'},
+        { action => 'PVEFW-reject', proto => 'udp', dport => '1024:65535', sport => 137 },
+        { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445', nbdport => 3 },
+        { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP
+        # Drop new/NotSyn traffic so that it doesn't get logged
+        "-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j DROP",
+        # Drop DNS replies
+        { action => 'DROP', proto => 'udp', sport => 53 },
+    ],
+    'PVEFW-tcpflags' => [
+        # same as shorewall tcpflags action.
+        # Packets arriving on this interface are checked for som illegal combinations of TCP flags
+        "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -g PVEFW-logflags",
+        "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -g PVEFW-logflags",
+        "-p tcp -m tcp --tcp-flags SYN,RST SYN,RST -g PVEFW-logflags",
+        "-p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -g PVEFW-logflags",
+        "-p tcp -m tcp --sport 0 --tcp-flags FIN,SYN,RST,ACK SYN -g PVEFW-logflags",
+    ],
+};
+
 # iptables -p icmp -h
 my $icmp_type_names = {
     any => 1,
@@ -1341,16 +1427,25 @@ my $rule_format = "%-15s %-30s %-30s %-15s %-15s %-15s\n";
 sub iptables_restore_cmdlist {
     my ($cmdlist) = @_;
 
-    run_command("/sbin/iptables-restore -n", input => $cmdlist);
+    run_command("/sbin/iptables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
+}
+
+sub ip6tables_restore_cmdlist {
+    my ($cmdlist) = @_;
+
+    run_command("/sbin/ip6tables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
 }
 
 sub ipset_restore_cmdlist {
     my ($cmdlist) = @_;
 
-    run_command("/usr/sbin/ipset restore", input => $cmdlist);
+    run_command("/usr/sbin/ipset restore", input => $cmdlist, errmsg => "ipset_restore_cmdlist");
 }
 
 sub iptables_get_chains {
+    my ($iptablescmd) = @_;
+
+    $iptablescmd = "iptables" if !$iptablescmd;
 
     my $res = {};
 
@@ -1405,7 +1500,7 @@ sub iptables_get_chains {
        }
     };
 
-    run_command("/sbin/iptables-save", outfunc => $parser);
+    run_command("/sbin/$iptablescmd-save", outfunc => $parser);
 
     return wantarray ? ($res, $hooks) : $res;
 }
@@ -1721,7 +1816,7 @@ sub ruleset_chain_add_conn_filters {
 }
 
 sub ruleset_chain_add_input_filters {
-    my ($ruleset, $chain, $options, $cluster_conf, $loglevel) = @_;
+    my ($ruleset, $chain, $options, $cluster_conf, $ipversion, $loglevel) = @_;
 
     if ($cluster_conf->{ipset}->{blacklist}){
        if (!ruleset_chain_exist($ruleset, "PVEFW-blacklist")) {
@@ -1734,7 +1829,9 @@ sub ruleset_chain_add_input_filters {
     }
 
     if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) {
-       ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
+       if ($ipversion == 4) {
+           ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
+       }
     }
 
     if ($options->{tcpflags}) {
@@ -1965,7 +2062,7 @@ sub enable_host_firewall {
     ruleset_addrule($ruleset, $chain, "-i lo -j ACCEPT");
 
     ruleset_chain_add_conn_filters($ruleset, $chain, 'ACCEPT');
-    ruleset_chain_add_input_filters($ruleset, $chain, $options, $cluster_conf, $loglevel);
+    ruleset_chain_add_input_filters($ruleset, $chain, $options, $cluster_conf, $ipversion, $loglevel);
 
     # we use RETURN because we need to check also tap rules
     my $accept_action = 'RETURN';
@@ -2959,7 +3056,9 @@ sub compile {
     }
 
     my ($ruleset, $ipset_ruleset) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose);
-    return ($ruleset, $ipset_ruleset);
+    my ($rulesetv6) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 6, $verbose);
+
+    return ($ruleset, $ipset_ruleset, $rulesetv6);
 }
 
 sub compile_iptables_filter {
@@ -3000,7 +3099,7 @@ sub compile_iptables_filter {
     ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -m set --match-set ${venet0_ipset_chain} src -j PVEFW-VENET-OUT");
 
     ruleset_create_chain($ruleset, "PVEFW-FWBR-IN");
-    ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options, $cluster_conf, $loglevel);
+    ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options, $cluster_conf, $ipversion, $loglevel);
 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-in fwln+ -j PVEFW-FWBR-IN");
 
@@ -3008,7 +3107,7 @@ sub compile_iptables_filter {
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-out fwln+ -j PVEFW-FWBR-OUT");
 
     ruleset_create_chain($ruleset, "PVEFW-VENET-IN");
-    ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options, $cluster_conf, $loglevel);
+    ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options, $cluster_conf, $ipversion, $loglevel);
 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN");
 
@@ -3155,11 +3254,11 @@ sub print_sig_rule {
 }
 
 sub get_ruleset_cmdlist {
-    my ($ruleset, $verbose) = @_;
+    my ($ruleset, $verbose, $iptablescmd) = @_;
 
     my $cmdlist = "*filter\n"; # we pass this to iptables-restore;
 
-    my ($active_chains, $hooks) = iptables_get_chains();
+    my ($active_chains, $hooks) = iptables_get_chains($iptablescmd);
     my $statushash = get_ruleset_status($ruleset, $active_chains, \&iptables_chain_digest, $verbose);
 
     # create missing chains first
@@ -3233,7 +3332,9 @@ sub get_ipset_cmdlist {
        }
     }
 
-    foreach my $chain (sort keys %$ruleset) {
+    # create -v4 and -v6 chains first
+    foreach my $chain (keys %$ruleset) {
+       next if $chain !~ m/-v[46]$/;
        my $stat = $statushash->{$chain};
        die "internal error" if !$stat;
 
@@ -3244,7 +3345,20 @@ sub get_ipset_cmdlist {
        }
     }
 
-    foreach my $chain (sort keys %$ruleset) {
+    # then create list chains which use above -v4 and -v6 chains
+    foreach my $chain (keys %$ruleset) {
+       next if $chain =~ m/-v[46]$/;
+       my $stat = $statushash->{$chain};
+       die "internal error" if !$stat;
+
+       if ($stat->{action} eq 'create') {
+           foreach my $cmd (@{$ruleset->{$chain}}) {
+               $cmdlist .= "$cmd\n";
+           }
+       }
+    }
+
+    foreach my $chain (keys %$ruleset) {
        my $stat = $statushash->{$chain};
        die "internal error" if !$stat;
 
@@ -3261,8 +3375,19 @@ sub get_ipset_cmdlist {
        }
     }
 
-    foreach my $chain (sort keys %$statushash) {
+    # remove unused list chains first
+    foreach my $chain (keys %$statushash) {
+       next if $statushash->{$chain}->{action} ne 'delete';
+       next if $chain !~ m/-v[46]$/;
+
+       $delete_cmdlist .= "flush $chain\n";
+       $delete_cmdlist .= "destroy $chain\n";
+    }
+
+    # the remove unused -v4 -v6 chains
+    foreach my $chain (keys %$statushash) {
        next if $statushash->{$chain}->{action} ne 'delete';
+       next if $chain =~ m/-v[46]$/;
 
        $delete_cmdlist .= "flush $chain\n";
        $delete_cmdlist .= "destroy $chain\n";
@@ -3274,7 +3399,7 @@ sub get_ipset_cmdlist {
 }
 
 sub apply_ruleset {
-    my ($ruleset, $hostfw_conf, $ipset_ruleset, $verbose) = @_;
+    my ($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6, $verbose) = @_;
 
     enable_bridge_firewall();
 
@@ -3282,6 +3407,7 @@ sub apply_ruleset {
        get_ipset_cmdlist($ipset_ruleset, undef, $verbose);
 
     my ($cmdlist, $changes) = get_ruleset_cmdlist($ruleset, $verbose);
+    my ($cmdlistv6, $changesv6) = get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables");
 
     if ($verbose) {
        if ($ipset_changes) {
@@ -3294,11 +3420,17 @@ sub apply_ruleset {
            print "iptables changes:\n";
            print $cmdlist;
        }
+
+       if ($changesv6) {
+           print "ip6tables changes:\n";
+           print $cmdlistv6;
+       }
     }
 
     ipset_restore_cmdlist($ipset_create_cmdlist);
 
     iptables_restore_cmdlist($cmdlist);
+    ip6tables_restore_cmdlist($cmdlistv6);
 
     ipset_restore_cmdlist($ipset_delete_cmdlist) if $ipset_delete_cmdlist;
 
@@ -3315,6 +3447,17 @@ sub apply_ruleset {
        }
     }
 
+    my $active_chainsv6 = iptables_get_chains("ip6tables");
+    my $statushashv6 = get_ruleset_status($rulesetv6, $active_chainsv6, \&iptables_chain_digest, 0);
+
+    foreach my $chain (sort keys %$rulesetv6) {
+       my $stat = $statushashv6->{$chain};
+       if ($stat->{action} ne 'exists') {
+           warn "unable to update chain '$chain'\n";
+           $errors = 1;
+       }
+    }
+
     die "unable to apply firewall changes\n" if $errors;
 
     update_nf_conntrack_max($hostfw_conf);
@@ -3359,7 +3502,16 @@ sub update_nf_conntrack_tcp_timeout_established {
 
 sub remove_pvefw_chains {
 
-    my ($chash, $hooks) = iptables_get_chains();
+    PVE::Firewall::remove_pvefw_chains_iptables("iptables");
+    PVE::Firewall::remove_pvefw_chains_iptables("ip6tables");
+    PVE::Firewall::remove_pvefw_chains_ipset();
+
+}
+
+sub remove_pvefw_chains_iptables {
+    my ($iptablescmd) = @_;
+
+    my ($chash, $hooks) = iptables_get_chains($iptablescmd);
     my $cmdlist = "*filter\n";
 
     foreach my $h (qw(INPUT OUTPUT FORWARD)) {
@@ -3377,18 +3529,33 @@ sub remove_pvefw_chains {
     }
     $cmdlist .= "COMMIT\n";
 
-    iptables_restore_cmdlist($cmdlist);
+    if($iptablescmd eq "ip6tables") {
+       ip6tables_restore_cmdlist($cmdlist);
+    } else {
+       iptables_restore_cmdlist($cmdlist);
+    }
+}
+
+sub remove_pvefw_chains_ipset {
 
     my $ipset_chains = ipset_get_chains();
 
-    $cmdlist = "";
+    my $sub_cmdlist = "";
+    my $cmdlist = "";
  
     foreach my $chain (keys %$ipset_chains) {
-       $cmdlist .= "flush $chain\n";
-       $cmdlist .= "destroy $chain\n";
+       if ($chain =~ m/^PVEFW-\S+\-(v4|v6)$/) {
+          $sub_cmdlist .= "flush $chain\n";
+          $sub_cmdlist .= "destroy $chain\n";
+       }else{
+          $cmdlist .= "flush $chain\n";
+          $cmdlist .= "destroy $chain\n";
+        }
     }
 
-    ipset_restore_cmdlist($cmdlist) if $cmdlist; 
+    $cmdlist .= $sub_cmdlist;
+
+    ipset_restore_cmdlist($cmdlist) if $cmdlist;
 }
 
 sub init {
@@ -3414,9 +3581,9 @@ sub update {
 
        my $hostfw_conf = load_hostfw_conf();
 
-       my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf);
+       my ($ruleset, $ipset_ruleset, $rulesetv6) = compile($cluster_conf, $hostfw_conf);
 
-       apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset);
+       apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6);
     };
 
     run_locked($code);