X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=4ed91dcb416127b014ba2d9e3ddf97339430cce7;hp=78507fb48d8fc664b06b73dbe2e1572b807d2b41;hb=3cc81077fbd2e9c4f8f945a0bf2f7fb2f829ece7;hpb=eba0fb64987d6f9193d11db6d0daad6e9503b10c diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 78507fb..4ed91dc 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -18,6 +18,8 @@ use PVE::Tools qw(run_command lock_file); use Data::Dumper; +# fixme: use ULOG instead of LOG? + my $nodename = PVE::INotify::nodename(); my $pve_fw_lock_filename = "/var/lock/pvefw.lck"; @@ -846,26 +848,31 @@ sub ruleset_insertrule { } sub generate_bridge_chains { - my ($ruleset, $hostfw_conf, $bridge) = @_; + my ($ruleset, $hostfw_conf, $bridge, $routing_table) = @_; + + my $options = $hostfw_conf->{options} || {}; + + die "error: detected direct route to bridge '$bridge'\n" + if !$options->{allow_bridge_route} && $routing_table->{$bridge}; if (!ruleset_chain_exist($ruleset, "$bridge-FW")) { ruleset_create_chain($ruleset, "$bridge-FW"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o $bridge -m physdev --physdev-is-bridged -j $bridge-FW"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i $bridge -m physdev --physdev-is-bridged -j $bridge-FW"); + ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o $bridge -m physdev --physdev-is-out -j $bridge-FW"); + ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i $bridge -m physdev --physdev-is-in -j $bridge-FW"); } if (!ruleset_chain_exist($ruleset, "$bridge-OUT")) { ruleset_create_chain($ruleset, "$bridge-OUT"); - ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-bridged --physdev-is-in -j $bridge-OUT"); - ruleset_addrule($ruleset, "PVEFW-INPUT", "-i $bridge -m physdev --physdev-is-bridged --physdev-is-in -j $bridge-OUT"); + ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-in -j $bridge-OUT"); + ruleset_insertrule($ruleset, "PVEFW-INPUT", "-i $bridge -m physdev --physdev-is-in -j $bridge-OUT"); } if (!ruleset_chain_exist($ruleset, "$bridge-IN")) { ruleset_create_chain($ruleset, "$bridge-IN"); - ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-bridged --physdev-is-out -j $bridge-IN"); + ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-out -j $bridge-IN"); ruleset_addrule($ruleset, "$bridge-FW", "-m mark --mark 1 -j ACCEPT"); # accept traffic to unmanaged bridge ports - ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-bridged --physdev-is-out -j ACCEPT "); + ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-out -j ACCEPT "); } } @@ -984,17 +991,27 @@ sub generate_venet_rules_direction { my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : "ACCEPT"; ruleset_add_chain_policy($ruleset, $chain, $policy, $loglevel, $accept_action); - # plug into FORWARD chain + # plug into FORWARD, INPUT and OUTPUT chain if ($direction eq 'OUT') { ruleset_generate_rule_insert($ruleset, "PVEFW-FORWARD", { action => $chain, source => $ip, iface_in => 'venet0'}); + + ruleset_generate_rule_insert($ruleset, "PVEFW-INPUT", { + action => $chain, + source => $ip, + iface_in => 'venet0'}); } else { ruleset_generate_rule($ruleset, "PVEFW-FORWARD", { action => $chain, dest => $ip, iface_out => 'venet0'}); + + ruleset_generate_rule($ruleset, "PVEFW-OUTPUT", { + action => $chain, + dest => $ip, + iface_out => 'venet0'}); } } @@ -1027,9 +1044,13 @@ sub generate_tap_rules_direction { ruleset_add_chain_policy($ruleset, $tapchain, $policy, $loglevel, $accept_action); # plug the tap chain to bridge chain - my $physdevdirection = $direction eq 'IN' ? "out" : "in"; - my $rule = "-m physdev --physdev-$physdevdirection $iface --physdev-is-bridged -j $tapchain"; - ruleset_insertrule($ruleset, "$bridge-$direction", $rule); + if ($direction eq 'IN') { + ruleset_insertrule($ruleset, "$bridge-IN", + "-m physdev --physdev-is-bridged --physdev-out $iface -j $tapchain"); + } else { + ruleset_insertrule($ruleset, "$bridge-OUT", + "-m physdev --physdev-in $iface -j $tapchain"); + } } sub enable_host_firewall { @@ -1050,7 +1071,7 @@ sub enable_host_firewall { ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate 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 conntrack --ctstate NEW -m multiport --dports 5404,5405 -j ACCEPT"); + ruleset_addrule($ruleset, $chain, "-p udp -m conntrack --ctstate NEW --dport 5404:5405 -j ACCEPT"); ruleset_addrule($ruleset, $chain, "-p udp -m udp --dport 9000 -j ACCEPT"); #corosync # we use RETURN because we need to check also tap rules @@ -1075,7 +1096,7 @@ sub enable_host_firewall { ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate 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 conntrack --ctstate NEW -m multiport --dports 5404,5405 -j ACCEPT"); + ruleset_addrule($ruleset, $chain, "-p udp -m conntrack --ctstate NEW --dport 5404:5405 -j ACCEPT"); ruleset_addrule($ruleset, $chain, "-p udp -m udp --dport 9000 -j ACCEPT"); #corosync # we use RETURN because we may want to check other thigs later @@ -1299,7 +1320,7 @@ sub parse_hostfw_option { my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog"; - if ($line =~ m/^(enable|dhcp|nosmurfs|tcpflags):\s*(0|1)\s*$/i) { + if ($line =~ m/^(enable|dhcp|nosmurfs|tcpflags|allow_bridge_route):\s*(0|1)\s*$/i) { $opt = lc($1); $value = int($2); } elsif ($line =~ m/^(log_level_in|log_level_out|tcp_flags_log_level|smurf_log_level):\s*(($loglevels)\s*)?$/i) { @@ -1591,10 +1612,40 @@ sub read_pvefw_status { return $status; } +# fixme: move to pve-common PVE::ProcFSTools +sub read_proc_net_route { + my $filename = "/proc/net/route"; + + my $res = {}; + + my $fh = IO::File->new ($filename, "r"); + return $res if !$fh; + + my $int_to_quad = sub { + return join '.' => map { ($_[0] >> 8*(3-$_)) % 256 } (3, 2, 1, 0); + }; + + while (defined(my $line = <$fh>)) { + next if $line =~/^Iface\s+Destination/; # skip head + my ($iface, $dest, $gateway, $metric, $mask, $mtu) = (split(/\s+/, $line))[0,1,2,6,7,8]; + push @{$res->{$iface}}, { + dest => &$int_to_quad(hex($dest)), + gateway => &$int_to_quad(hex($gateway)), + mask => &$int_to_quad(hex($mask)), + metric => $metric, + mtu => $mtu, + }; + } + + return $res; +} + sub compile { my $vmdata = read_local_vm_config(); my $vmfw_configs = read_vm_firewall_configs($vmdata); + my $routing_table = read_proc_net_route(); + my $groups_conf = {}; my $filename = "/etc/pve/firewall/groups.fw"; if (my $fh = IO::File->new($filename, O_RDONLY)) { @@ -1641,7 +1692,7 @@ sub compile { $bridge .= "v$net->{tag}" if $net->{tag}; - generate_bridge_chains($ruleset, $hostfw_conf, $bridge); + generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table); my $macaddr = $net->{macaddr}; generate_tap_rules_direction($ruleset, $groups_conf, $iface, $netid, $macaddr, $vmfw_conf, $bridge, 'IN'); @@ -1672,7 +1723,10 @@ sub compile { warn "no bridge device for CT $vmid iface '$netid'\n"; next; # fixme? } - my $macaddr = $d->{host_mac}; + + generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table); + + my $macaddr = $d->{mac}; my $iface = $d->{host_ifname}; generate_tap_rules_direction($ruleset, $groups_conf, $iface, $netid, $macaddr, $vmfw_conf, $bridge, 'IN'); generate_tap_rules_direction($ruleset, $groups_conf, $iface, $netid, $macaddr, $vmfw_conf, $bridge, 'OUT'); @@ -1686,7 +1740,11 @@ sub compile { # fixme: what log level should we use here? my $loglevel = get_option_log_level($hostfw_options, "log_level_out"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -j ACCEPT"); + # fixme: should we really block inter-bridge traffic? + + # always allow traffic from containers? + ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -j RETURN"); + # disable interbridge routing ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j PVEFW-Drop"); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j PVEFW-Drop"); @@ -1821,10 +1879,12 @@ sub get_rulset_cmdlist { } sub apply_ruleset { - my ($ruleset, $verbose) = @_; + my ($ruleset, $hostfw_conf, $verbose) = @_; enable_bridge_firewall(); + update_nf_conntrack_max($hostfw_conf); + my $cmdlist = get_rulset_cmdlist($ruleset, $verbose); print $cmdlist if $verbose; @@ -1878,13 +1938,11 @@ sub update { my ($ruleset, $hostfw_conf) = PVE::Firewall::compile(); - update_nf_conntrack_max($hostfw_conf); - if ($start || $status eq 'active') { save_pvefw_status('active') if ($status ne 'active'); - PVE::Firewall::apply_ruleset($ruleset, $verbose); + apply_ruleset($ruleset, $hostfw_conf, $verbose); } else { print "Firewall not active (status = $status)\n" if $verbose; }