]> git.proxmox.com Git - pve-firewall.git/blobdiff - PVE/Firewall.pm
use accept mark for security groups
[pve-firewall.git] / PVE / Firewall.pm
index 4b14871e907a8a949e22017fbd12617126149e49..ec6112afe914146d4b15e603cd2d1135fac919c3 100644 (file)
@@ -3,7 +3,7 @@ package PVE::Firewall;
 use warnings;
 use strict;
 use Data::Dumper;
-use Digest::MD5;
+use Digest::SHA;
 use PVE::Tools;
 use PVE::QemuServer;
 use File::Path;
@@ -137,6 +137,18 @@ sub parse_port_name_number_or_range {
     return ($nbports);
 }
 
+my $bridge_firewall_enabled = 0;
+
+sub enable_bridge_firewall {
+
+    return if $bridge_firewall_enabled; # only once
+
+    system("echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables");
+    system("echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables");
+
+    $bridge_firewall_enabled = 1;
+}
+
 my $rule_format = "%-15s %-30s %-30s %-15s %-15s %-15s\n";
 
 sub iptables {
@@ -159,12 +171,11 @@ sub iptables_get_chains {
     my $is_pvefw_chain = sub {
        my $name = shift;
 
-       return 1 if $name =~ m/^BRIDGEFW-(:?IN|OUT)$/;
-       return 1 if $name =~ m/^proxmoxfw-\S+$/;
+       return 1 if $name =~ m/^PVEFW-\S+$/;
+
        return 1 if $name =~ m/^tap\d+i\d+-(:?IN|OUT)$/;
        return 1 if $name =~ m/^vmbr\d+-(:?IN|OUT)$/;
        return 1 if $name =~ m/^GROUP-(:?[^\s\-]+)-(:?IN|OUT)$/;
-       return 1 if $name =~ m/^host-(:?IN|OUT)$/;
 
        return undef;
     };
@@ -188,7 +199,7 @@ sub iptables_get_chains {
            my $chain = $1;
            return if !&$is_pvefw_chain($chain);
            $res->{$chain} = "unknown";
-       } elsif ($line =~ m/^-A\s+(\S+)\s.*--log-prefix\s+\"PVESIG:(\S+)\"/) {
+       } elsif ($line =~ m/^-A\s+(\S+)\s.*--comment\s+\"PVESIG:(\S+)\"/) {
            my ($chain, $sig) = ($1, $2);
            return if !&$is_pvefw_chain($chain);
            $res->{$chain} = $sig;
@@ -226,7 +237,7 @@ sub iptables_rule_exist {
 }
 
 sub ruleset_generate_rule {
-    my ($ruleset, $chain, $rule) = @_;
+    my ($ruleset, $chain, $rule, $goto) = @_;
 
     my $cmd = '';
 
@@ -239,7 +250,11 @@ sub ruleset_generate_rule {
     $cmd .= " --dport $rule->{dport}" if $rule->{dport};
     $cmd .= "  --match multiport" if $rule->{nbsport} && $rule->{nbsport} > 1;
     $cmd .= " --sport $rule->{sport}" if $rule->{sport};
-    $cmd .= " -j $rule->{action}" if $rule->{action};
+
+    if (my $action = $rule->{action}) {
+       $goto = 1 if !defined($goto) && $action eq 'PVEFW-SET-ACCEPT-MARK';
+       $cmd .= $goto ? " -g $action" : " -j $action";
+    };
 
     ruleset_addrule($ruleset, $chain, $cmd) if $cmd;
 }
@@ -247,6 +262,8 @@ sub ruleset_generate_rule {
 sub ruleset_create_chain {
     my ($ruleset, $chain) = @_;
 
+    die "Invalid chain name '$chain' (28 char max)\n" if length($chain) > 28;
+
     die "chain '$chain' already exists\n" if $ruleset->{$chain};
 
     $ruleset->{$chain} = [];
@@ -277,38 +294,38 @@ sub ruleset_insertrule {
 sub generate_bridge_chains {
     my ($ruleset, $bridge) = @_;
 
-    if (!ruleset_chain_exist($ruleset, "BRIDGEFW-IN")){
-       ruleset_create_chain($ruleset, "BRIDGEFW-IN");
+    if (!ruleset_chain_exist($ruleset, "PVEFW-BRIDGE-IN")){
+       ruleset_create_chain($ruleset, "PVEFW-BRIDGE-IN");
     }
 
-    if (!ruleset_chain_exist($ruleset, "BRIDGEFW-OUT")){
-       ruleset_create_chain($ruleset, "BRIDGEFW-OUT");
+    if (!ruleset_chain_exist($ruleset, "PVEFW-BRIDGE-OUT")){
+       ruleset_create_chain($ruleset, "PVEFW-BRIDGE-OUT");
     }
 
-    if (!ruleset_chain_exist($ruleset, "proxmoxfw-FORWARD")){
-       ruleset_create_chain($ruleset, "proxmoxfw-FORWARD");
+    if (!ruleset_chain_exist($ruleset, "PVEFW-FORWARD")){
+       ruleset_create_chain($ruleset, "PVEFW-FORWARD");
 
-       ruleset_addrule($ruleset, "proxmoxfw-FORWARD", "-m state --state RELATED,ESTABLISHED -j ACCEPT");
-       ruleset_addrule($ruleset, "proxmoxfw-FORWARD", "-m physdev --physdev-is-in --physdev-is-bridged -j BRIDGEFW-OUT");
-       ruleset_addrule($ruleset, "proxmoxfw-FORWARD", "-m physdev --physdev-is-out --physdev-is-bridged -j BRIDGEFW-IN");
+       ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m state --state RELATED,ESTABLISHED -j ACCEPT");
+       ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-in --physdev-is-bridged -j PVEFW-BRIDGE-OUT");
+       ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-out --physdev-is-bridged -j PVEFW-BRIDGE-IN");
     }
 
     if (!ruleset_chain_exist($ruleset, "$bridge-IN")) {
        ruleset_create_chain($ruleset, "$bridge-IN");
-       ruleset_addrule($ruleset, "proxmoxfw-FORWARD", "-i $bridge -j DROP");  # disable interbridge routing
-       ruleset_addrule($ruleset, "BRIDGEFW-IN", "-j $bridge-IN");
+       ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i $bridge -j DROP");  # disable interbridge routing
+       ruleset_addrule($ruleset, "PVEFW-BRIDGE-IN", "-j $bridge-IN");
        ruleset_addrule($ruleset, "$bridge-IN", "-j ACCEPT");
     }
 
     if (!ruleset_chain_exist($ruleset, "$bridge-OUT")) {
        ruleset_create_chain($ruleset, "$bridge-OUT");
-       ruleset_addrule($ruleset, "proxmoxfw-FORWARD", "-o $bridge -j DROP"); # disable interbridge routing
-       ruleset_addrule($ruleset, "BRIDGEFW-OUT", "-j $bridge-OUT");
+       ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o $bridge -j DROP"); # disable interbridge routing
+       ruleset_addrule($ruleset, "PVEFW-BRIDGE-OUT", "-j $bridge-OUT");
     }
 }
 
 sub generate_tap_rules_direction {
-    my ($ruleset, $iface, $netid, $rules, $bridge, $direction) = @_;
+    my ($ruleset, $iface, $netid, $macaddr, $rules, $bridge, $direction) = @_;
 
     my $tapchain = "$iface-$direction";
 
@@ -317,6 +334,10 @@ sub generate_tap_rules_direction {
     ruleset_addrule($ruleset, $tapchain, "-m state --state INVALID -j DROP");
     ruleset_addrule($ruleset, $tapchain, "-m state --state RELATED,ESTABLISHED -j ACCEPT");
 
+    if ($direction eq 'OUT' && defined($macaddr)) {
+       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;
@@ -326,11 +347,14 @@ sub generate_tap_rules_direction {
                if(!ruleset_chain_exist($ruleset, $rule->{action})){
                    generate_group_rules($ruleset, $2);
                }
+               ruleset_generate_rule($ruleset, $tapchain, $rule);
+               ruleset_addrule($ruleset, $tapchain, "-m mark --mark 1 -g $bridge-IN");
+           } else {
+               # we go to vmbr-IN if accept in out rules
+               $rule->{action} = "$bridge-IN" if $rule->{action} eq 'ACCEPT' && $direction eq 'OUT';
+               ruleset_generate_rule($ruleset, $tapchain, $rule);
            }
-           # we go to vmbr-IN if accept in out rules
-           $rule->{action} = "$bridge-IN" if $rule->{action} eq 'ACCEPT' && $direction eq 'OUT';
-           ruleset_generate_rule($ruleset, $tapchain, $rule);
-        }
+       }
     }
 
     ruleset_addrule($ruleset, $tapchain, "-j LOG --log-prefix \"$tapchain-dropped: \" --log-level 4");
@@ -344,7 +368,7 @@ sub generate_tap_rules_direction {
     if ($direction eq 'OUT'){
        # add tap->host rules
        my $rule = "-m physdev --physdev-$physdevdirection $iface -j $tapchain";
-       ruleset_addrule($ruleset, "proxmoxfw-INPUT", $rule);
+       ruleset_addrule($ruleset, "PVEFW-INPUT", $rule);
     }
 }
 
@@ -358,48 +382,51 @@ sub enablehostfw {
     my $rules = parse_fw_rules($filename, $fh);
 
     # host inbound firewall
-    ruleset_create_chain($ruleset, "host-IN");
+    my $chain = "PVEFW-HOST-IN";
+    ruleset_create_chain($ruleset, $chain);
 
-    ruleset_addrule($ruleset, "host-IN", "-m state --state INVALID -j DROP");
-    ruleset_addrule($ruleset, "host-IN", "-m state --state RELATED,ESTABLISHED -j ACCEPT");
-    ruleset_addrule($ruleset, "host-IN", "-i lo -j ACCEPT");
-    ruleset_addrule($ruleset, "host-IN", "-m addrtype --dst-type MULTICAST -j ACCEPT");
-    ruleset_addrule($ruleset, "host-IN", "-p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
-    ruleset_addrule($ruleset, "host-IN", "-p udp -m udp --dport 9000 -j ACCEPT");  #corosync
+    ruleset_addrule($ruleset, $chain, "-m state --state INVALID -j DROP");
+    ruleset_addrule($ruleset, $chain, "-m state --state RELATED,ESTABLISHED -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-i lo -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-m addrtype --dst-type MULTICAST -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-p udp -m udp --dport 9000 -j ACCEPT");  #corosync
 
     if ($rules->{in}) {
         foreach my $rule (@{$rules->{in}}) {
             # we use RETURN because we need to check also tap rules
             $rule->{action} = 'RETURN' if $rule->{action} eq 'ACCEPT';
-            ruleset_generate_rule($ruleset, "host-IN", $rule);
+            ruleset_generate_rule($ruleset, $chain, $rule);
         }
     }
 
-    ruleset_addrule($ruleset, "host-IN", "-j LOG --log-prefix \"kvmhost-IN dropped: \" --log-level 4");
-    ruleset_addrule($ruleset, "host-IN", "-j DROP");
+    ruleset_addrule($ruleset, $chain, "-j LOG --log-prefix \"kvmhost-IN dropped: \" --log-level 4");
+    ruleset_addrule($ruleset, $chain, "-j DROP");
 
     # host outbound firewall
-    ruleset_create_chain($ruleset, "host-OUT");
-    ruleset_addrule($ruleset, "host-OUT", "-m state --state INVALID -j DROP");
-    ruleset_addrule($ruleset, "host-OUT", "-m state --state RELATED,ESTABLISHED -j ACCEPT");
-    ruleset_addrule($ruleset, "host-OUT", "-o lo -j ACCEPT");
-    ruleset_addrule($ruleset, "host-OUT", "-m addrtype --dst-type MULTICAST -j ACCEPT");
-    ruleset_addrule($ruleset, "host-OUT", "-p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
-    ruleset_addrule($ruleset, "host-OUT", "-p udp -m udp --dport 9000 -j ACCEPT"); #corosync
+    $chain = "PVEFW-HOST-OUT";
+    ruleset_create_chain($ruleset, $chain);
+
+    ruleset_addrule($ruleset, $chain, "-m state --state INVALID -j DROP");
+    ruleset_addrule($ruleset, $chain, "-m state --state RELATED,ESTABLISHED -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-o lo -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-m addrtype --dst-type MULTICAST -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-p udp -m udp --dport 9000 -j ACCEPT"); #corosync
 
     if ($rules->{out}) {
         foreach my $rule (@{$rules->{out}}) {
             # we use RETURN because we need to check also tap rules
             $rule->{action} = 'RETURN' if $rule->{action} eq 'ACCEPT';
-            ruleset_generate_rule($ruleset, "host-OUT", $rule);
+            ruleset_generate_rule($ruleset, $chain, $rule);
         }
     }
 
-    ruleset_addrule($ruleset, "host-OUT", "-j LOG --log-prefix \"kvmhost-OUT dropped: \" --log-level 4");
-    ruleset_addrule($ruleset, "host-OUT", "-j DROP");
+    ruleset_addrule($ruleset, $chain, "-j LOG --log-prefix \"kvmhost-OUT dropped: \" --log-level 4");
+    ruleset_addrule($ruleset, $chain, "-j DROP");
     
-    ruleset_addrule($ruleset, "proxmoxfw-OUTPUT", "-j host-OUT");
-    ruleset_addrule($ruleset, "proxmoxfw-INPUT", "-j host-IN");
+    ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-j PVEFW-HOST-OUT");
+    ruleset_addrule($ruleset, "PVEFW-INPUT", "-j PVEFW-HOST-IN");
 }
 
 sub generate_group_rules {
@@ -424,13 +451,15 @@ sub generate_group_rules {
     $chain = "GROUP-${group}-OUT";
 
     ruleset_create_chain($ruleset, $chain);
+    ruleset_addrule($ruleset, $chain, "-j MARK --set-mark 0"); # clear mark
 
     if ($rules->{out}) {
         foreach my $rule (@{$rules->{out}}) {
-            # we go the BRIDGEFW-IN because we need to check also other tap rules 
-            # (and group rules can be set on any bridge, so we can't go to VMBRXX-IN)
-            $rule->{action} = 'BRIDGEFW-IN' if $rule->{action} eq 'ACCEPT';
-            ruleset_generate_rule($rule, $chain, $rule);
+            # we go the PVEFW-SET-ACCEPT-MARK Instead of ACCEPT) because we need to 
+           # check also other tap rules (and group rules can be set on any bridge, 
+           # so we can't go to VMBRXX-IN)
+            $rule->{action} = 'PVEFW-SET-ACCEPT-MARK' if $rule->{action} eq 'ACCEPT';
+            ruleset_generate_rule($ruleset, $chain, $rule);
         }
     }
 }
@@ -596,8 +625,11 @@ sub compile {
     my $ruleset = {};
 
     # setup host firewall rules
-    ruleset_create_chain($ruleset, "proxmoxfw-INPUT");
-    ruleset_create_chain($ruleset, "proxmoxfw-OUTPUT");
+    ruleset_create_chain($ruleset, "PVEFW-INPUT");
+    ruleset_create_chain($ruleset, "PVEFW-OUTPUT");
+
+    ruleset_create_chain($ruleset, "PVEFW-SET-ACCEPT-MARK");
+    ruleset_addrule($ruleset, "PVEFW-SET-ACCEPT-MARK", "-j MARK --set-mark 1");
 
     enablehostfw($ruleset);
 
@@ -619,8 +651,9 @@ sub compile {
 
            generate_bridge_chains($ruleset, $bridge);
 
-           generate_tap_rules_direction($ruleset, $iface, $netid, $rules->{$vmid}->{in}, $bridge, 'IN');
-           generate_tap_rules_direction($ruleset, $iface, $netid, $rules->{$vmid}->{out}, $bridge, 'OUT');
+           my $macaddr = $net->{macaddr};
+           generate_tap_rules_direction($ruleset, $iface, $netid, $macaddr, $rules->{$vmid}->{in}, $bridge, 'IN');
+           generate_tap_rules_direction($ruleset, $iface, $netid, $macaddr, $rules->{$vmid}->{out}, $bridge, 'OUT');
        }
     }
     return $ruleset;
@@ -634,7 +667,7 @@ sub get_ruleset_status {
     my $statushash = {};
 
     foreach my $chain (sort keys %$ruleset) {
-       my $digest = Digest::MD5->new();
+       my $digest = Digest::SHA->new('sha1');
        foreach my $cmd (@{$ruleset->{$chain}}) {
             $digest->add("$cmd\n");
        }
@@ -678,15 +711,14 @@ sub print_ruleset {
 sub print_sig_rule {
     my ($chain, $sig) = @_;
 
-    # Note: This rule should never match! We just use this hack to store a SHA1 checksum
-    # used to detect changes
-    return "-A $chain -j LOG --log-prefix \"PVESIG:$sig\" -p tcp -s \"127.128.129.130\" --dport 1\n";
+    # We just use this to store a SHA1 checksum used to detect changes
+    return "-A $chain -m comment --comment \"PVESIG:$sig\"\n";
 }
 
-sub compile_and_start {
-    my ($verbose) = @_;
+sub apply_ruleset {
+    my ($ruleset, $verbose) = @_;
 
-    my $ruleset = compile();
+    enable_bridge_firewall();
 
     my $cmdlist = "*filter\n"; # we pass this to iptables-restore;
 
@@ -701,16 +733,16 @@ sub compile_and_start {
        $cmdlist .= ":$chain - [0:0]\n";
     }
 
-    my $rule = "INPUT -j proxmoxfw-INPUT";
+    my $rule = "INPUT -j PVEFW-INPUT";
     if (!PVE::Firewall::iptables_rule_exist($rule)) {
        $cmdlist .= "-A $rule\n";
     }
-    $rule = "OUTPUT -j proxmoxfw-OUTPUT";
+    $rule = "OUTPUT -j PVEFW-OUTPUT";
     if (!PVE::Firewall::iptables_rule_exist($rule)) {
        $cmdlist .= "-A $rule\n";
     }
 
-    $rule = "FORWARD -j proxmoxfw-FORWARD";
+    $rule = "FORWARD -j PVEFW-FORWARD";
     if (!PVE::Firewall::iptables_rule_exist($rule)) {
        $cmdlist .= "-A $rule\n";
     }
@@ -726,8 +758,7 @@ sub compile_and_start {
            }
            $cmdlist .= print_sig_rule($chain, $stat->{sig});
        } elsif ($stat->{action} eq 'delete') {
-           $cmdlist .= "-F $chain\n";
-           $cmdlist .= "-X $chain\n";
+           die "internal error"; # this should not happen
        } elsif ($stat->{action} eq 'exists') {
            # do nothing
        } else {
@@ -735,6 +766,15 @@ sub compile_and_start {
        }
     }
 
+    foreach my $chain (keys %$statushash) {
+       next if $statushash->{$chain}->{action} ne 'delete';
+       $cmdlist .= "-F $chain\n";
+    }
+    foreach my $chain (keys %$statushash) {
+       next if $statushash->{$chain}->{action} ne 'delete';
+       $cmdlist .= "-X $chain\n";
+    }
+
     $cmdlist .= "COMMIT\n";
 
     print $cmdlist if $verbose;