From c5e8b0088f2f51897b8b22a587c091e4e5bf3251 Mon Sep 17 00:00:00 2001 From: Alexandre Derumier Date: Wed, 28 Mar 2018 10:53:28 +0200 Subject: [PATCH] compile ebtables rules -A FORWARD -j PVEFW-FORWARD -A PVEFW-FORWARD -p IPv4 -j ACCEPT #filter mac in iptables for ipv4, so we can speedup rules with conntrack established -A PVEFW-FORWARD -p IPv6 -j ACCEPT -A PVEFW-FORWARD -o fwln+ -j PVEFW-FWBR-OUT -A PVEFW-FWBR-OUT -i tap110i0 -j tap110i0-OUT -A tap110i0-OUT -s ! 36:97:15:91:19:3c -j DROP -A tap110i0-OUT -p ARP -j ACCEPT -A tap110i0-OUT -j DROP -A tap110i0-OUT -j ACCEPT -A PVEFW-FWBR-OUT -i veth130.1 -j veth130.1-OUT -A veth130.1-OUT -s ! 36:95:a9:ae:f5:ec -j DROP -A veth130.1-OUT -j ACCEPT Signed-off-by: Alexandre Derumier Signed-off-by: Wolfgang Bumiller Reviewed-by: Thomas Lamprecht Tested-by: Thomas Lamprecht --- debian/example/100.fw | 3 + src/PVE/Firewall.pm | 105 +++++++++++++++++++++++++++++++- src/PVE/Service/pve_firewall.pm | 6 +- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/debian/example/100.fw b/debian/example/100.fw index 7a8da48..3a08b07 100644 --- a/debian/example/100.fw +++ b/debian/example/100.fw @@ -9,6 +9,9 @@ enable: 1 # disable/enable MAC address filter macfilter: 0 +# limit layer2 specific protocols +layer2_protocols: ARP,802_1Q,IPX,NetBEUI,PPP + # default policy policy_in: DROP policy_out: REJECT diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index c8a430c..82c6ac9 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -2548,6 +2548,14 @@ sub parse_fw_rule { return $rule; } +sub verify_ethertype { + my ($value) = @_; + my $types = get_etc_ethertypes(); + die "unknown ethernet protocol type: $value\n" + if !defined($types->{byname}->{$value}) && + !defined($types->{byid}->{$value}); +} + sub parse_vmfw_option { my ($line) = @_; @@ -2567,6 +2575,10 @@ sub parse_vmfw_option { } elsif ($line =~ m/^(ips_queues):\s*((\d+)(:(\d+))?)\s*$/i) { $opt = lc($1); $value = $2; + } elsif ($line =~ m/^(layer2_protocols):\s*(((\S+)[,]?)+)\s*$/i) { + $opt = lc($1); + $value = $2; + verify_ethertype($_) foreach split(/\s*,\s*/, $value); } else { die "can't parse option '$line'\n" } @@ -3380,9 +3392,10 @@ sub compile { my $ruleset = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose); my $rulesetv6 = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 6, $verbose); + my $ebtables_ruleset = compile_ebtables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $verbose); my $ipset_ruleset = compile_ipsets($cluster_conf, $vmfw_configs, $vmdata); - return ($ruleset, $ipset_ruleset, $rulesetv6); + return ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset); } sub compile_iptables_filter { @@ -3594,6 +3607,94 @@ sub compile_ipsets { return $ipset_ruleset; } +sub compile_ebtables_filter { + my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $verbose) = @_; + + return ({}, {}) if !$cluster_conf->{options}->{enable}; + + my $ruleset = {}; + + ruleset_create_chain($ruleset, "PVEFW-FORWARD"); + + + ruleset_create_chain($ruleset, "PVEFW-FWBR-OUT"); + #for ipv4 and ipv6, check macaddress in iptables, so we use conntrack 'ESTABLISHED', to speedup rules + ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-p IPv4', '-j ACCEPT'); + ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-p IPv6', '-j ACCEPT'); + ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-o fwln+', '-j PVEFW-FWBR-OUT'); + + # generate firewall rules for QEMU VMs + foreach my $vmid (keys %{$vmdata->{qemu}}) { + eval { + my $conf = $vmdata->{qemu}->{$vmid}; + my $vmfw_conf = $vmfw_configs->{$vmid}; + return if !$vmfw_conf; + + foreach my $netid (keys %$conf) { + next if $netid !~ m/^net(\d+)$/; + my $net = PVE::QemuServer::parse_net($conf->{$netid}); + next if !$net->{firewall}; + my $iface = "tap${vmid}i$1"; + my $macaddr = $net->{macaddr}; + + generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid); + + } + }; + warn $@ if $@; # just to be sure - should not happen + } + + # generate firewall rules for LXC containers + foreach my $vmid (keys %{$vmdata->{lxc}}) { + eval { + my $conf = $vmdata->{lxc}->{$vmid}; + + my $vmfw_conf = $vmfw_configs->{$vmid}; + return if !$vmfw_conf || !$vmfw_conf->{options}->{enable}; + + foreach my $netid (keys %$conf) { + next if $netid !~ m/^net(\d+)$/; + my $net = PVE::LXC::Config->parse_lxc_network($conf->{$netid}); + next if !$net->{firewall}; + my $iface = "veth${vmid}i$1"; + my $macaddr = $net->{hwaddr}; + generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid); + } + }; + warn $@ if $@; # just to be sure - should not happen + } + + return $ruleset; +} + +sub generate_tap_layer2filter { + my ($ruleset, $iface, $macaddr, $vmfw_conf, $vmid) = @_; + my $options = $vmfw_conf->{options}; + + my $tapchain = $iface."-OUT"; + + # ebtables remove zeros from mac pairs + $macaddr =~ s/0([0-9a-f])/$1/ig; + $macaddr = lc($macaddr); + + ruleset_create_chain($ruleset, $tapchain); + + if (defined($macaddr) && !(defined($options->{macfilter}) && $options->{macfilter} == 0)) { + ruleset_addrule($ruleset, $tapchain, "-s ! $macaddr", '-j DROP'); + } + + if (defined($options->{layer2_protocols})){ + foreach my $proto (split(/,/, $options->{layer2_protocols})) { + ruleset_addrule($ruleset, $tapchain, "-p $proto", '-j ACCEPT'); + } + ruleset_addrule($ruleset, $tapchain, '', "-j DROP"); + } else { + ruleset_addrule($ruleset, $tapchain, '', '-j ACCEPT'); + } + + ruleset_addrule($ruleset, 'PVEFW-FWBR-OUT', "-i $iface", "-j $tapchain"); +} + sub get_ruleset_status { my ($ruleset, $active_chains, $digest_fn, $verbose) = @_; @@ -3947,7 +4048,7 @@ sub update { my $hostfw_conf = load_hostfw_conf($cluster_conf); - my ($ruleset, $ipset_ruleset, $rulesetv6) = compile($cluster_conf, $hostfw_conf); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf); apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6); }; diff --git a/src/PVE/Service/pve_firewall.pm b/src/PVE/Service/pve_firewall.pm index 0ba8a84..a9a3435 100755 --- a/src/PVE/Service/pve_firewall.pm +++ b/src/PVE/Service/pve_firewall.pm @@ -164,7 +164,7 @@ __PACKAGE__->register_method ({ if ($status eq 'running') { - my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); $verbose = 0; # do not show iptables details my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose); @@ -201,7 +201,7 @@ __PACKAGE__->register_method ({ my $verbose = 1; my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); - my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); print "ipset cmdlist:\n"; my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose); @@ -329,7 +329,7 @@ __PACKAGE__->register_method ({ local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog - my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose}); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose}); PVE::FirewallSimulator::debug($param->{verbose} || 0); -- 2.39.2