X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=ef74ca2fae597a882ea30e778c49cee8cfc5ff77;hp=3ccf95271b66530c81f1663055fe3eecaee6afec;hb=15c800003e921cfd627ef0b6213b9ff09fe3d8c8;hpb=fe3d79b4807b47eeeedb232e84c022a4dd6828b2 diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 3ccf952..ef74ca2 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -12,6 +12,7 @@ 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 File::Basename; use File::Path; use IO::File; @@ -49,7 +50,15 @@ our $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+'; my $max_alias_name_length = 64; my $max_ipset_name_length = 64; -my $max_group_name_length = 20; +my $max_group_name_length = 18; + +my $PROTOCOLS_WITH_PORTS = { + udp => 1, 17 => 1, + udplite => 1, 136 => 1, + tcp => 1, 6 => 1, + dccp => 1, 33 => 1, + sctp => 1, 132 => 1, +}; PVE::JSONSchema::register_format('IPorCIDR', \&pve_verify_ip_or_cidr); sub pve_verify_ip_or_cidr { @@ -321,6 +330,10 @@ my $pve_fw_macros = { { action => 'PARAM', proto => 'tcp', dport => '465' }, { action => 'PARAM', proto => 'tcp', dport => '587' }, ], + 'MDNS' => [ + "Multicast DNS", + { action => 'PARAM', proto => 'udp', dport => '5353' }, + ], 'Munin' => [ "Munin networked resource monitoring traffic", { action => 'PARAM', proto => 'tcp', dport => '4949' }, @@ -875,24 +888,6 @@ sub get_etc_protocols { return $etc_protocols; } -my $ipv4_mask_hash_localnet = { - '255.255.0.0' => 16, - '255.255.128.0' => 17, - '255.255.192.0' => 18, - '255.255.224.0' => 19, - '255.255.240.0' => 20, - '255.255.248.0' => 21, - '255.255.252.0' => 22, - '255.255.254.0' => 23, - '255.255.255.0' => 24, - '255.255.255.128' => 25, - '255.255.255.192' => 26, - '255.255.255.224' => 27, - '255.255.255.240' => 28, - '255.255.255.248' => 29, - '255.255.255.252' => 30, -}; - my $__local_network; sub local_network { @@ -916,13 +911,17 @@ sub local_network { my $mask; if ($isv6) { $mask = $entry->{prefix}; + next if !$mask; # skip the default route... } else { - $mask = $ipv4_mask_hash_localnet->{$entry->{mask}}; + $mask = $PVE::Network::ipv4_mask_hash_localnet->{$entry->{mask}}; next if !defined($mask); } my $cidr = "$entry->{dest}/$mask"; my $testnet = Net::IP->new($cidr); - if ($testnet->overlaps($testip) == $Net::IP::IP_B_IN_A_OVERLAP) { + my $overlap = $testnet->overlaps($testip); + if ($overlap == $Net::IP::IP_B_IN_A_OVERLAP || + $overlap == $Net::IP::IP_IDENTICAL) + { $__local_network = $cidr; return; } @@ -1124,6 +1123,125 @@ sub copy_list_with_digest { return wantarray ? ($res, $digest) : $res; } +our $cluster_option_properties = { + enable => { + description => "Enable or disable the firewall cluster wide.", + type => 'integer', + minimum => 0, + optional => 1, + }, + policy_in => { + description => "Input policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'REJECT', 'DROP'], + }, + policy_out => { + description => "Output policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'REJECT', 'DROP'], + }, +}; + +our $host_option_properties = { + enable => { + description => "Enable host firewall rules.", + type => 'boolean', + optional => 1, + }, + log_level_in => get_standard_option('pve-fw-loglevel', { + description => "Log level for incoming traffic." }), + log_level_out => get_standard_option('pve-fw-loglevel', { + description => "Log level for outgoing traffic." }), + tcp_flags_log_level => get_standard_option('pve-fw-loglevel', { + description => "Log level for illegal tcp flags filter." }), + smurf_log_level => get_standard_option('pve-fw-loglevel', { + description => "Log level for SMURFS filter." }), + nosmurfs => { + description => "Enable SMURFS filter.", + type => 'boolean', + optional => 1, + }, + tcpflags => { + description => "Filter illegal combinations of TCP flags.", + type => 'boolean', + optional => 1, + }, + nf_conntrack_max => { + description => "Maximum number of tracked connections.", + type => 'integer', + optional => 1, + minimum => 32768, + }, + nf_conntrack_tcp_timeout_established => { + description => "Conntrack established timeout.", + type => 'integer', + optional => 1, + minimum => 7875, + }, + ndp => { + description => "Enable NDP.", + type => 'boolean', + optional => 1, + }, +}; + +our $vm_option_properties = { + enable => { + description => "Enable/disable firewall rules.", + type => 'boolean', + optional => 1, + }, + macfilter => { + description => "Enable/disable MAC address filter.", + type => 'boolean', + optional => 1, + }, + dhcp => { + description => "Enable DHCP.", + type => 'boolean', + optional => 1, + }, + ndp => { + description => "Enable NDP.", + type => 'boolean', + optional => 1, + }, + radv => { + description => "Allow sending Router Advertisement.", + type => 'boolean', + optional => 1, + }, + ipfilter => { + description => "Enable default IP filters. " . + "This is equivalent to adding an empty ipfilter-net ipset " . + "for every interface. Such ipsets implicitly contain sane default " . + "restrictions such as restricting IPv6 link local addresses to " . + "the one derived from the interface's MAC address. For containers " . + "the configured IP addresses will be implicitly added.", + type => 'boolean', + optional => 1, + }, + policy_in => { + description => "Input policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'REJECT', 'DROP'], + }, + policy_out => { + description => "Output policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'REJECT', 'DROP'], + }, + log_level_in => get_standard_option('pve-fw-loglevel', { + description => "Log level for incoming traffic." }), + log_level_out => get_standard_option('pve-fw-loglevel', { + description => "Log level for outgoing traffic." }), + +}; + my $addr_list_descr = "This can refer to a single IP address, an IP set ('+ipsetname') or an IP alias definition. You can also specify an address range like '20.34.101.207-201.3.9.99', or a list of IP addresses and networks (entries are separated by comma). Please do not mix IPv4 and IPv6 addresses inside such lists."; @@ -1138,6 +1256,7 @@ my $rule_properties = { }, digest => get_standard_option('pve-config-digest'), type => { + description => "Rule type.", type => 'string', optional => 1, enum => ['in', 'out', 'group'], @@ -1151,6 +1270,7 @@ my $rule_properties = { minLength => 2, }, macro => { + description => "Use predefined standard macro.", type => 'string', optional => 1, maxLength => 128, @@ -1175,6 +1295,7 @@ my $rule_properties = { optional => 1, }, enable => { + description => "Flag to enable/disable a rule.", type => 'integer', minimum => 0, optional => 1, @@ -1190,6 +1311,7 @@ my $rule_properties = { optional => 1, }, comment => { + description => "Descriptive comment.", type => 'string', optional => 1, }, @@ -1392,15 +1514,22 @@ sub verify_rule { if ($rule->{dport}) { eval { parse_port_name_number_or_range($rule->{dport}, 1); }; &$add_error('dport', $@) if $@; + my $proto = $rule->{proto}; &$add_error('proto', "missing property - 'dport' requires this property") - if !$rule->{proto}; + if !$proto; + &$add_error('dport', "protocol '$proto' does not support ports") + if !$PROTOCOLS_WITH_PORTS->{$proto} && + $proto ne 'icmp' && $proto ne 'icmpv6'; # special cases } if ($rule->{sport}) { eval { parse_port_name_number_or_range($rule->{sport}, 0); }; &$add_error('sport', $@) if $@; + my $proto = $rule->{proto}; &$add_error('proto', "missing property - 'sport' requires this property") - if !$rule->{proto}; + if !$proto; + &$add_error('sport', "protocol '$proto' does not support ports") + if !$PROTOCOLS_WITH_PORTS->{$proto}; } if ($rule->{source}) { @@ -1721,8 +1850,8 @@ sub ruleset_generate_cmdstr { } } - if ($rule->{proto}) { - push @cmd, "-p $rule->{proto}"; + if (my $proto = $rule->{proto}) { + push @cmd, "-p $proto"; my $multiport = 0; $multiport++ if $nbdport > 1; @@ -1734,16 +1863,18 @@ sub ruleset_generate_cmdstr { if ($multiport == 2) && ($rule->{dport} ne $rule->{sport}); if ($rule->{dport}) { - if ($rule->{proto} && $rule->{proto} eq 'icmp') { + if ($proto eq 'icmp') { # Note: we use dport to store --icmp-type die "unknown icmp-type '$rule->{dport}'\n" if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}}); push @cmd, "-m icmp --icmp-type $rule->{dport}"; - } elsif ($rule->{proto} && $rule->{proto} eq 'icmpv6') { + } elsif ($proto eq 'icmpv6') { # Note: we use dport to store --icmpv6-type die "unknown icmpv6-type '$rule->{dport}'\n" if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}}); push @cmd, "-m icmpv6 --icmpv6-type $rule->{dport}"; + } elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) { + die "protocol $proto does not have ports\n"; } else { if ($nbdport > 1) { if ($multiport == 2) { @@ -1758,6 +1889,8 @@ sub ruleset_generate_cmdstr { } if ($rule->{sport}) { + die "protocol $proto does not have ports\n" + if !$PROTOCOLS_WITH_PORTS->{$proto}; if ($nbsport > 1) { push @cmd, "--sports $rule->{sport}" if $multiport != 2; } else { @@ -3249,13 +3382,13 @@ sub compile_iptables_filter { } # generate firewall rules for QEMU VMs - foreach my $vmid (keys %{$vmdata->{qemu}}) { + foreach my $vmid (sort keys %{$vmdata->{qemu}}) { eval { my $conf = $vmdata->{qemu}->{$vmid}; my $vmfw_conf = $vmfw_configs->{$vmid}; return if !$vmfw_conf; - foreach my $netid (keys %$conf) { + foreach my $netid (sort keys %$conf) { next if $netid !~ m/^net(\d+)$/; my $net = PVE::QemuServer::parse_net($conf->{$netid}); next if !$net->{firewall}; @@ -3272,14 +3405,14 @@ sub compile_iptables_filter { } # generate firewall rules for LXC containers - foreach my $vmid (keys %{$vmdata->{lxc}}) { + foreach my $vmid (sort keys %{$vmdata->{lxc}}) { eval { my $conf = $vmdata->{lxc}->{$vmid}; my $vmfw_conf = $vmfw_configs->{$vmid}; return if !$vmfw_conf; if ($vmfw_conf->{options}->{enable}) { - foreach my $netid (keys %$conf) { + foreach my $netid (sort keys %$conf) { next if $netid !~ m/^net(\d+)$/; my $net = PVE::LXC::Config->parse_lxc_network($conf->{$netid}); next if !$net->{firewall}; @@ -3404,10 +3537,10 @@ sub compile_ipsets { { cidr => $linklocal }, { cidr => 'fe80::/10', nomatch => 1 } ]; - if ($net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) { + if (defined($net->{ip}) && $net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) { push @$set, { cidr => $1 }; } - if ($net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) { + if (defined($net->{ip6}) && $net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) { push @$set, { cidr => $1 }; } }