X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=c4971091c47e00301ad297fb94f1b59d7e7f318a;hb=6f6a6b3f8259c06fe9f7f14490caa5275996b5c6;hp=72d5a69ae50d2e19bd3fe037da44a502000c194b;hpb=585ede439c205271353a17b64ad89da717df87fc;p=pve-firewall.git diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 72d5a69..c497109 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -3,27 +3,27 @@ package PVE::Firewall; use warnings; use strict; -use POSIX; -use Data::Dumper; use Digest::SHA; -use Socket qw(AF_INET6 inet_ntop inet_pton); -use PVE::INotify; -use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(register_standard_option get_standard_option); -use PVE::Cluster; -use PVE::ProcFSTools; -use PVE::Tools qw($IPV4RE $IPV6RE); -use PVE::Network; -use PVE::SafeSyslog; +use Encode; use File::Basename; use File::Path; use IO::File; use Net::IP; -use PVE::Tools qw(run_command lock_file dir_glob_foreach); -use Encode; +use POSIX; +use Socket qw(AF_INET AF_INET6 inet_ntop inet_pton); use Storable qw(dclone); -my $hostfw_conf_filename = "/etc/pve/local/host.fw"; +use PVE::Cluster; +use PVE::Corosync; +use PVE::Exception qw(raise raise_param_exc); +use PVE::INotify; +use PVE::JSONSchema qw(register_standard_option get_standard_option); +use PVE::Network; +use PVE::ProcFSTools; +use PVE::SafeSyslog; +use PVE::Tools qw($IPV4RE $IPV6RE); +use PVE::Tools qw(run_command lock_file dir_glob_foreach); + my $pvefw_conf_dir = "/etc/pve/firewall"; my $clusterfw_conf_filename = "$pvefw_conf_dir/cluster.fw"; @@ -127,6 +127,7 @@ eval { }; my $nodename = PVE::INotify::nodename(); +my $hostfw_conf_filename = "/etc/pve/nodes/$nodename/host.fw"; my $pve_fw_lock_filename = "/var/lock/pvefw.lck"; @@ -666,14 +667,10 @@ $pve_std_chains_conf->{6} = { #{ 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' }, { match => '-p tcp', target => '-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", + { match => '-p udp', target => '-j REJECT --reject-with icmp6-port-unreachable' }, + { target => '-j REJECT --reject-with icmp6-adm-prohibited' }, ], 'PVEFW-Drop' => [ # same as shorewall 'Drop', which is equal to DROP, @@ -2391,21 +2388,35 @@ sub generate_tap_rules_direction { # plug the tap chain to bridge chain if ($direction eq 'IN') { ruleset_addrule($ruleset, "PVEFW-FWBR-IN", - "-m physdev --physdev-is-bridged --physdev-out $iface", "-j $tapchain", $loglevel, 'FWBR-IN: ', $vmid); + "-m physdev --physdev-is-bridged --physdev-out $iface", "-j $tapchain"); } else { ruleset_addrule($ruleset, "PVEFW-FWBR-OUT", - "-m physdev --physdev-is-bridged --physdev-in $iface", "-j $tapchain", $loglevel, 'FWBR-OUT: ', $vmid); + "-m physdev --physdev-is-bridged --physdev-in $iface", "-j $tapchain"); } } sub enable_host_firewall { - my ($ruleset, $hostfw_conf, $cluster_conf, $ipversion) = @_; + my ($ruleset, $hostfw_conf, $cluster_conf, $ipversion, $corosync_conf) = @_; my $options = $hostfw_conf->{options}; my $cluster_options = $cluster_conf->{options}; my $rules = $hostfw_conf->{rules}; my $cluster_rules = $cluster_conf->{rules}; + # corosync preparation + my $corosync_rule = "-p udp --dport 5404:5405"; + my $corosync_local_addresses = {}; + my $local_hostname = PVE::INotify::nodename(); + if (defined($corosync_conf)) { + PVE::Corosync::for_all_corosync_addresses($corosync_conf, $ipversion, sub { + my ($node_name, $node_ip, $node_ipversion, $key) = @_; + + if ($node_name eq $local_hostname) { + $corosync_local_addresses->{$key} = $node_ip; + } + }); + } + # host inbound firewall my $chain = "PVEFW-HOST-IN"; ruleset_create_chain($ruleset, $chain); @@ -2450,14 +2461,23 @@ sub enable_host_firewall { ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 3128", "-j $accept_action"); # SPICE Proxy ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 22", "-j $accept_action"); # SSH - my $localnet = $cluster_conf->{aliases}->{local_network}->{cidr}; - my $localnet_ver = $cluster_conf->{aliases}->{local_network}->{ipversion}; + # corosync inbound rules + if (defined($corosync_conf)) { + # always allow multicast + ruleset_addrule($ruleset, $chain, "-m addrtype --dst-type MULTICAST $corosync_rule", "-j $accept_action"); - # corosync - if ($localnet && ($ipversion == $localnet_ver)) { - my $corosync_rule = "-p udp --dport 5404:5405"; - ruleset_addrule($ruleset, $chain, "-s $localnet -d $localnet $corosync_rule", "-j $accept_action"); - ruleset_addrule($ruleset, $chain, "-s $localnet -m addrtype --dst-type MULTICAST $corosync_rule", "-j $accept_action"); + PVE::Corosync::for_all_corosync_addresses($corosync_conf, $ipversion, sub { + my ($node_name, $node_ip, $node_ipversion, $key) = @_; + + if ($node_name ne $local_hostname) { + my $destination = $corosync_local_addresses->{$key}; + + # accept only traffic on same ring + if (defined($destination)) { + ruleset_addrule($ruleset, $chain, "-d $destination -s $node_ip $corosync_rule", "-j $accept_action"); + } + } + }); } # implement input policy @@ -2500,15 +2520,33 @@ sub enable_host_firewall { } # allow standard traffic on cluster network + my $localnet = $cluster_conf->{aliases}->{local_network}->{cidr}; + my $localnet_ver = $cluster_conf->{aliases}->{local_network}->{ipversion}; + if ($localnet && ($ipversion == $localnet_ver)) { ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 8006", "-j $accept_action"); # PVE API ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 22", "-j $accept_action"); # SSH ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 5900:5999", "-j $accept_action"); # PVE VNC Console ruleset_addrule($ruleset, $chain, "-d $localnet -p tcp --dport 3128", "-j $accept_action"); # SPICE Proxy + } - my $corosync_rule = "-p udp --dport 5404:5405"; - ruleset_addrule($ruleset, $chain, "-d $localnet $corosync_rule", "-j $accept_action"); + # corosync outbound rules + if (defined($corosync_conf)) { + # always allow multicast ruleset_addrule($ruleset, $chain, "-m addrtype --dst-type MULTICAST $corosync_rule", "-j $accept_action"); + + PVE::Corosync::for_all_corosync_addresses($corosync_conf, $ipversion, sub { + my ($node_name, $node_ip, $node_ipversion, $key) = @_; + + if ($node_name ne $local_hostname) { + my $source = $corosync_local_addresses->{$key}; + + # accept only traffic on same ring + if (defined($source)) { + ruleset_addrule($ruleset, $chain, "-s $source -d $node_ip $corosync_rule", "-j $accept_action"); + } + } + }); } # implement output policy @@ -2801,21 +2839,27 @@ sub parse_alias { sub generic_fw_config_parser { my ($filename, $cluster_conf, $empty_conf, $rule_env) = @_; - my $fh = IO::File->new($filename, O_RDONLY); - return {} if !$fh; - my $section; my $group; my $res = $empty_conf; - while (defined(my $line = <$fh>)) { + my $raw; + if ($filename =~ m!^/etc/pve/(.*)$!) { + $raw = PVE::Cluster::get_config($1); + } else { + $raw = eval { PVE::Tools::file_get_contents($filename) }; # ignore errors + } + return {} if !$raw; + + my $linenr = 0; + while ($raw =~ /^\h*(.*?)\h*$/gm) { + my $line = $1; + $linenr++; next if $line =~ m/^#/; next if $line =~ m/^\s*$/; - chomp $line; - my $linenr = $fh->input_line_number(); my $prefix = "$filename (line $linenr)"; if ($empty_conf->{options} && ($line =~ m/^\[options\]$/i)) { @@ -3454,7 +3498,7 @@ sub save_hostfw_conf { } sub compile { - my ($cluster_conf, $hostfw_conf, $vmdata) = @_; + my ($cluster_conf, $hostfw_conf, $vmdata, $corosync_conf) = @_; my $vmfw_configs; @@ -3475,6 +3519,9 @@ sub compile { $hostfw_conf = load_hostfw_conf($cluster_conf, undef) if !$hostfw_conf; + # cfs_update is handled by daemon or API + $corosync_conf = PVE::Cluster::cfs_read_file("corosync.conf") if !$corosync_conf; + $vmdata = read_local_vm_config(); $vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, undef); } @@ -3494,8 +3541,8 @@ sub compile { push @{$cluster_conf->{ipset}->{management}}, { cidr => $localnet }; - my $ruleset = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4); - my $rulesetv6 = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 6); + my $ruleset = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $corosync_conf, 4); + my $rulesetv6 = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $corosync_conf, 6); my $ebtables_ruleset = compile_ebtables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata); my $ipset_ruleset = compile_ipsets($cluster_conf, $vmfw_configs, $vmdata); @@ -3503,7 +3550,7 @@ sub compile { } sub compile_iptables_filter { - my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion) = @_; + my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $corosync_conf, $ipversion) = @_; my $ruleset = {}; @@ -3533,7 +3580,7 @@ sub compile_iptables_filter { my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0)); if ($hostfw_enable) { - eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf, $ipversion); }; + eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf, $ipversion, $corosync_conf); }; warn $@ if $@; # just to be sure - should not happen } @@ -3780,7 +3827,12 @@ sub compile_ebtables_filter { push(@$arpfilter, $ip); } } - push(@$arpfilter, $net->{ip}) if $net->{ip} && $vmfw_conf->{options}->{ipfilter}; + if (defined(my $ip = $net->{ip}) && $vmfw_conf->{options}->{ipfilter}) { + # ebtables changes this to a .0/MASK network but we just + # want the address here, no network - see #2193 + $ip =~ s|/(\d+)$||; + push @$arpfilter, $ip; + } generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid, $arpfilter); } };