]> git.proxmox.com Git - pve-firewall.git/blobdiff - PVE/Firewall.pm
correctly apply macros
[pve-firewall.git] / PVE / Firewall.pm
index b4e262b1cf8d94e630fb92efe51a928bf5f26521..8cd8a1bf297e3e14896b5d12b77caab8a96088ff 100644 (file)
@@ -348,6 +348,103 @@ my $pve_fw_macros = {
 my $pve_fw_parsed_macros;
 my $pve_fw_preferred_macro_names = {};
 
+my $pve_std_chains = {
+    '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' },
+       { action => 'DROP', dsttype => 'MULTICAST' },
+       { action => 'DROP', dsttype => 'ANYCAST' },
+       { 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 => 'icmp' },
+       "-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 => 'icmp', dport => 'fragmentation-needed' },
+       { action => 'ACCEPT', proto => 'icmp', dport => 'time-exceeded' },
+       # 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 => 'icmp', dport => 'fragmentation-needed' },
+       { action => 'ACCEPT', proto => 'icmp', dport => 'time-exceeded' },
+       # 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-logflags' => [
+       # same as shorewall logflags action. (fixme: enable/disable logging)
+       "-j LOG --log-prefix \"logflags-dropped:\" --log-level 4 --log-ip-options",
+       "-j DROP",
+    ],
+    '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",
+    ],
+    'PVEFW-smurflog' => [
+       # same as shorewall smurflog. (fixme: enable/disable logging)
+       "-j LOG --log-prefix \"smurfs-dropped\" --log-level 4",
+       "-j DROP",
+    ],
+    'PVEFW-smurfs' => [
+       # same as shorewall smurfs action
+       # Filter packets for smurfs (packets with a broadcast address as the source).
+       "-s 0.0.0.0/32 -j RETURN",
+       "-m addrtype --src-type BROADCAST -g PVEFW-smurflog",
+       "-s 224.0.0.0/4 -g PVEFW-smurflog",
+    ],
+};
+
 # iptables -p icmp -h
 my $icmp_type_names = {
     any => 1,
@@ -732,13 +829,23 @@ sub generate_tap_rules_direction {
 
     ruleset_create_chain($ruleset, $tapchain);
 
+    if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) {
+       ruleset_addrule($ruleset, $tapchain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
+    }
+
+    if ($options->{tcpflags}) {
+       ruleset_addrule($ruleset, $tapchain, "-p tcp -j PVEFW-tcpflags");
+    }
+
     ruleset_addrule($ruleset, $tapchain, "-m conntrack --ctstate INVALID -j DROP");
     ruleset_addrule($ruleset, $tapchain, "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT");
 
-    if ($direction eq 'OUT' && defined($macaddr)) {
+    if ($direction eq 'OUT' && defined($macaddr) && 
+       !(defined($options->{macfilter}) && $options->{macfilter} == 0)) {
        ruleset_addrule($ruleset, $tapchain, "-m mac ! --mac-source $macaddr -j DROP");
     }
 
+
     if ($rules) {
         foreach my $rule (@$rules) {
            next if $rule->{iface} && $rule->{iface} ne $netid;
@@ -775,11 +882,13 @@ sub generate_tap_rules_direction {
            ruleset_addrule($ruleset, $tapchain, "-j ACCEPT");
        }
     } elsif ($policy eq 'DROP') {
+       ruleset_addrule($ruleset, $tapchain, "-j PVEFW-Drop");
        ruleset_addrule($ruleset, $tapchain, "-j LOG --log-prefix \"$tapchain-dropped: \" --log-level 4");
        ruleset_addrule($ruleset, $tapchain, "-j DROP");
     } elsif ($policy eq 'REJECT') {
+       ruleset_addrule($ruleset, $tapchain, "-j PVEFW-Reject");
        ruleset_addrule($ruleset, $tapchain, "-j LOG --log-prefix \"$tapchain-reject: \" --log-level 4");
-       ruleset_addrule($ruleset, $tapchain, "-j REJECT");
+       ruleset_addrule($ruleset, $tapchain, "-g PVEFW-reject");
     } else {
        # should not happen
        die "internal error: unknown policy '$policy'";
@@ -970,19 +1079,33 @@ sub parse_fw_rule {
     if ($macro) {
        foreach my $templ (@$macro) {
            my $rule = {};
+           my $param_used = {};
            foreach my $k (keys %$templ) {
                my $v = $templ->{$k};
                if ($v eq 'PARAM') {
                    $v = $param->{$k};
+                   $param_used->{$k} = 1;
                } elsif ($v eq 'DEST') {
                    $v = $param->{dest};
+                   $param_used->{dest} = 1;
                } elsif ($v eq 'SOURCE') {
                    $v = $param->{source};
+                   $param_used->{source} = 1;
                }
 
                die "missing parameter '$k' in macro '$macro_name'\n" if !defined($v);
                $rule->{$k} = $v;
            }
+           foreach my $k (keys %$param) {
+               next if !defined($param->{$k});
+               next if $param_used->{$k};
+               if (defined($rule->{$k})) {
+                   die "parameter '$k' already define in macro (value = '$rule->{$k}')\n"
+                       if $rule->{$k} ne $param->{$k};
+               } else {
+                   $rule->{$k} = $param->{$k};
+               }
+           }
            push @$rules, $rule;
        }
     } else {
@@ -1004,13 +1127,13 @@ sub parse_fw_option {
 
     my ($opt, $value);
 
-    if ($line =~ m/^enable:\s*(0|1)\s*$/i) {
-       $opt = 'enable';
-       $value = int($1);
+    if ($line =~ m/^(enable|macfilter|nosmurfs|tcpflags):\s*(0|1)\s*$/i) {
+       $opt = lc($1);
+       $value = int($2);
     } elsif ($line =~ m/^(policy-(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) {
        $opt = lc($1);
        $value = uc($3);
-     } else {
+    } else {
        chomp $line;
        die "can't parse option '$line'\n"
     }
@@ -1186,6 +1309,21 @@ sub read_vm_firewall_rules {
     return $rules;
 }
 
+sub generate_std_chains {
+    my ($ruleset) = @_;
+
+    foreach my $chain (keys %$pve_std_chains) {
+       ruleset_create_chain($ruleset, $chain);
+       foreach my $rule (@{$pve_std_chains->{$chain}}) {
+           if (ref($rule)) {
+               ruleset_generate_rule($ruleset, $chain, $rule);
+           } else {
+               ruleset_addrule($ruleset, $chain, $rule);
+           }
+       }
+    }
+}
+
 sub compile {
     my $vmdata = read_local_vm_config();
     my $rules = read_vm_firewall_rules($vmdata);
@@ -1204,8 +1342,7 @@ sub compile {
     ruleset_create_chain($ruleset, "PVEFW-OUTPUT");
     ruleset_create_chain($ruleset, "PVEFW-FORWARD");
 
-    ruleset_create_chain($ruleset, "PVEFW-SET-ACCEPT-MARK");
-    ruleset_addrule($ruleset, "PVEFW-SET-ACCEPT-MARK", "-j MARK --set-mark 1");
+    generate_std_chains($ruleset);
 
     my $enable_hostfw = 0;
     $filename = "/etc/pve/local/host.fw";