X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=fd83ba32438ae3787f61a7884eb83a7fb1ee4dc9;hp=31b9ad123cf1e47b89a1f8ff07bf52b7ae758464;hb=b4deedab3b7a041c0a7b5bd0bd2ac6644e041464;hpb=097820b037b12789bc4080d221284147303f5ca2 diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 31b9ad1..fd83ba3 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -40,7 +40,7 @@ sub pve_verify_ipv4_or_cidr { my ($cidr, $noerr) = @_; if ($cidr =~ m!^(?:$IPV4RE)(/(\d+))?$!) { - return $cidr if Net::IP->new($cidr); + return $cidr if Net::IP->new($cidr); return undef if $noerr; die Net::IP::Error() . "\n"; } @@ -53,7 +53,7 @@ PVE::JSONSchema::register_standard_option('ipset-name', { type => 'string', pattern => '[A-Za-z][A-Za-z0-9\-\_]+', minLength => 2, - maxLength => 20, + maxLength => 20, }); PVE::JSONSchema::register_standard_option('pve-fw-alias', { @@ -61,12 +61,12 @@ PVE::JSONSchema::register_standard_option('pve-fw-alias', { type => 'string', pattern => '[A-Za-z][A-Za-z0-9\-\_]+', minLength => 2, - maxLength => 20, + maxLength => 20, }); PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => { description => "Log level.", - type => 'string', + type => 'string', enum => ['emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info', 'debug', 'nolog'], optional => 1, }); @@ -79,7 +79,7 @@ PVE::JSONSchema::register_standard_option('pve-security-group-name', { type => 'string', pattern => $security_group_name_pattern, minLength => 2, - maxLength => 20, + maxLength => 20, }); my $feature_ipset_nomatch = 0; @@ -733,7 +733,7 @@ sub parse_address_list { } $iprange = 1 if $elem =~ m/-/; } - + die "you can use a range in a list\n" if $iprange && $count > 1; } @@ -757,7 +757,7 @@ sub parse_port_name_number_or_range { if ($icmp_type_names->{$item}) { $icmp_port = 1; } else { - die "invalid port '$item'\n" if !$services->{byname}->{$item}; + die "invalid port '$item'\n" if !$services->{byname}->{$item}; } } } @@ -918,7 +918,7 @@ sub add_rule_properties { sub delete_rule_properties { my ($rule, $delete_str) = @_; - + foreach my $opt (PVE::Tools::split_list($delete_str)) { raise_param_exc({ 'delete' => "no such property ('$opt')"}) if !defined($rule_properties->{$opt}); @@ -956,7 +956,7 @@ my $apply_macro = sub { if (!defined($v)) { my $msg = "missing parameter '$k' in macro '$macro_name'"; - raise_param_exc({ macro => $msg }) if $verify; + raise_param_exc({ macro => $msg }) if $verify; die "$msg\n"; } $rule->{$k} = $v; @@ -968,7 +968,7 @@ my $apply_macro = sub { if (defined($rule->{$k})) { if ($rule->{$k} ne $param->{$k}) { my $msg = "parameter '$k' already define in macro (value = '$rule->{$k}')"; - raise_param_exc({ $k => $msg }) if $verify; + raise_param_exc({ $k => $msg }) if $verify; die "$msg\n"; } } else { @@ -988,19 +988,19 @@ sub verify_rule { raise_param_exc({ type => "missing property"}) if !$type; raise_param_exc({ action => "missing property"}) if !$rule->{action}; - + if ($type eq 'in' || $type eq 'out') { raise_param_exc({ action => "unknown action '$rule->{action}'"}) if $rule->{action} !~ m/^(ACCEPT|DROP|REJECT)$/; } elsif ($type eq 'group') { - raise_param_exc({ type => "security groups not allowed"}) + raise_param_exc({ type => "security groups not allowed"}) if !$allow_groups; - raise_param_exc({ action => "invalid characters in security group name"}) + raise_param_exc({ action => "invalid characters in security group name"}) if $rule->{action} !~ m/^${security_group_name_pattern}$/; } else { raise_param_exc({ type => "unknown rule type '$type'"}); } - + # fixme: verify $rule->{iface}? if ($rule->{macro}) { @@ -1243,7 +1243,7 @@ sub ruleset_generate_cmdstr { die "invalid security group name '$dest'\n"; } } elsif ($dest =~ m/^${ip_alias_pattern}$/){ - my $alias = lc($source); + my $alias = lc($dest); my $e = $cluster_conf->{aliases}->{$alias}; die "no such alias $dest" if !$e; push @cmd, "-d $e->{cidr}"; @@ -1436,7 +1436,12 @@ sub ruleset_chain_add_conn_filters { } sub ruleset_chain_add_input_filters { - my ($ruleset, $chain, $options) = @_; + my ($ruleset, $chain, $options, $cluster_conf, $loglevel) = @_; + + if ($cluster_conf->{ipset}->{blacklist}){ + ruleset_addlog($ruleset, $chain, 0, "DROP: ", $loglevel, "-m set --match-set PVEFW-blacklist src"); + ruleset_addrule($ruleset, $chain, "-m set --match-set PVEFW-blacklist src -j DROP"); + } if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) { ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs"); @@ -1471,6 +1476,26 @@ sub ruleset_create_vm_chain { } } +sub ruleset_add_group_rule { + my ($ruleset, $cluster_conf, $chain, $rule, $direction, $action) = @_; + + my $group = $rule->{action}; + my $group_chain = "GROUP-$group-$direction"; + if(!ruleset_chain_exist($ruleset, $group_chain)){ + generate_group_rules($ruleset, $cluster_conf, $group); + } + + if ($direction eq 'OUT' && $rule->{iface_out}) { + ruleset_addrule($ruleset, $chain, "-o $rule->{iface_out} -j $group_chain"); + } elsif ($direction eq 'IN' && $rule->{iface_in}) { + ruleset_addrule($ruleset, $chain, "-i $rule->{iface_in} -j $group_chain"); + } else { + ruleset_addrule($ruleset, $chain, "-j $group_chain"); + } + + ruleset_addrule($ruleset, $chain, "-m mark --mark 1 -j $action"); +} + sub ruleset_generate_vm_rules { my ($ruleset, $rules, $cluster_conf, $chain, $netid, $direction, $options) = @_; @@ -1482,28 +1507,18 @@ sub ruleset_generate_vm_rules { next if $rule->{iface} && $rule->{iface} ne $netid; next if !$rule->{enable}; if ($rule->{type} eq 'group') { - my $group_chain = "GROUP-$rule->{action}-$direction"; - if(!ruleset_chain_exist($ruleset, $group_chain)){ - generate_group_rules($ruleset, $cluster_conf, $rule->{action}); - } - ruleset_addrule($ruleset, $chain, "-j $group_chain"); - if ($direction eq 'OUT'){ - ruleset_addrule($ruleset, $chain, "-m mark --mark 1 -j RETURN"); - }else{ - my $accept = generate_nfqueue($options); - ruleset_addrule($ruleset, $chain, "-m mark --mark 1 -j $accept"); - } - + ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, $direction, + $direction eq 'OUT' ? 'RETURN' : $in_accept); } else { next if $rule->{type} ne $lc_direction; eval { if ($direction eq 'OUT') { ruleset_generate_rule($ruleset, $chain, $rule, - { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, + { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, undef, $cluster_conf); } else { - ruleset_generate_rule($ruleset, $chain, $rule, - { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }, + ruleset_generate_rule($ruleset, $chain, $rule, + { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }, undef, $cluster_conf); } }; @@ -1649,7 +1664,7 @@ sub enable_host_firewall { ruleset_addrule($ruleset, $chain, "-i lo -j ACCEPT"); ruleset_chain_add_conn_filters($ruleset, $chain, 'ACCEPT'); - ruleset_chain_add_input_filters($ruleset, $chain, $options); + ruleset_chain_add_input_filters($ruleset, $chain, $options, $cluster_conf, $loglevel); ruleset_addrule($ruleset, $chain, "-m addrtype --dst-type MULTICAST -j ACCEPT"); ruleset_addrule($ruleset, $chain, "-p udp -m conntrack --ctstate NEW --dport 5404:5405 -j ACCEPT"); @@ -1660,8 +1675,14 @@ sub enable_host_firewall { # add host rules first, so that cluster wide rules can be overwritten foreach my $rule (@$rules, @$cluster_rules) { - next if $rule->{type} ne 'in'; - ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf); + $rule->{iface_in} = $rule->{iface} if $rule->{iface}; + if ($rule->{type} eq 'group') { + ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action); + } elsif ($rule->{type} eq 'in') { + ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, + undef, $cluster_conf); + } + delete $rule->{iface_in}; } # implement input policy @@ -1687,8 +1708,14 @@ sub enable_host_firewall { # add host rules first, so that cluster wide rules can be overwritten foreach my $rule (@$rules, @$cluster_rules) { - next if $rule->{type} ne 'out'; - ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf); + $rule->{iface_out} = $rule->{iface} if $rule->{iface}; + if ($rule->{type} eq 'group') { + ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'OUT', $accept_action); + } elsif ($rule->{type} eq 'out') { + ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, + undef, $cluster_conf); + } + delete $rule->{iface_out}; } # implement output policy @@ -1701,10 +1728,14 @@ sub enable_host_firewall { sub generate_group_rules { my ($ruleset, $cluster_conf, $group) = @_; - die "no such security group '$group'\n" if !$cluster_conf->{groups}->{$group}; my $rules = $cluster_conf->{groups}->{$group}; + if (!$rules) { + warn "no such security group '$group'\n"; + $rules = []; # create empty chain + } + my $chain = "GROUP-${group}-IN"; ruleset_create_chain($ruleset, $chain); @@ -1777,7 +1808,7 @@ sub parse_fw_rule { die "unknown action '$action'\n"; } } elsif ($type eq 'group') { - die "wrong number of rule elements\n" if scalar(@data) != 3; + die "wrong number of rule elements\n" if scalar(@data) > 3; die "groups disabled\n" if !$allow_groups; die "invalid characters in group name\n" if $action !~ m/^${security_group_name_pattern}$/; @@ -1853,7 +1884,7 @@ sub parse_hostfw_option { my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog"; - if ($line =~ m/^(enable|nosmurfs|tcpflags|optimize):\s*(0|1)\s*$/i) { + if ($line =~ m/^(enable|nosmurfs|tcpflags):\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) { @@ -2012,14 +2043,14 @@ sub parse_cluster_fw_rules { my $section; my $group; - my $res = { - rules => [], - options => {}, - aliases => {}, - groups => {}, - group_comments => {}, + my $res = { + rules => [], + options => {}, + aliases => {}, + groups => {}, + group_comments => {}, ipset => {} , - ipset_comments => {}, + ipset_comments => {}, }; while (defined(my $line = <$fh>)) { @@ -2059,7 +2090,7 @@ sub parse_cluster_fw_rules { $group = lc($1); my $comment = $2; $res->{$section}->{$group} = []; - $res->{ipset_comments}->{$group} = decode('utf8', $comment) + $res->{ipset_comments}->{$group} = decode('utf8', $comment) if $comment; next; } @@ -2107,7 +2138,7 @@ sub parse_cluster_fw_rules { if($cidr !~ m/^${ip_alias_pattern}$/) { $cidr =~ s|/32$||; - + eval { pve_verify_ipv4_or_cidr($cidr); }; if (my $err = $@) { warn "$prefix: $cidr - $err"; @@ -2115,10 +2146,10 @@ sub parse_cluster_fw_rules { } } - my $entry = { cidr => $cidr }; + my $entry = { cidr => $cidr }; $entry->{nomatch} = 1 if $nomatch; $entry->{comment} = $comment if $comment; - + push @{$res->{$section}->{$group}}, $entry; } } @@ -2274,7 +2305,7 @@ my $format_ipset = sub { if $entry->{comment} && $entry->{comment} !~ m/^\s*$/; $raw .= "$line\n"; } - + return $raw; }; @@ -2469,7 +2500,7 @@ sub save_clusterfw_conf { my $aliases = $cluster_conf->{aliases}; $raw .= &$format_aliases($aliases) if scalar(keys %$aliases); - + foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) { if (my $comment = $cluster_conf->{ipset_comments}->{$ipset}) { my $utf8comment = encode('utf8', $comment); @@ -2570,7 +2601,7 @@ sub compile { ruleset_create_chain($ruleset, "PVEFW-OUTPUT"); ruleset_create_chain($ruleset, "PVEFW-FORWARD"); - + my $hostfw_options = $hostfw_conf->{options} || {}; # fixme: what log level should we use here? @@ -2578,25 +2609,20 @@ sub compile { ruleset_chain_add_conn_filters($ruleset, "PVEFW-FORWARD", "ACCEPT"); - if ($cluster_conf->{ipset}->{blacklist}){ - ruleset_addlog($ruleset, "PVEFW-FORWARD", 0, "DROP: ", $loglevel, "-m set --match-set PVEFW-blacklist src"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m set --match-set PVEFW-blacklist src -j DROP"); - } - ruleset_create_chain($ruleset, "PVEFW-VENET-OUT"); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -j PVEFW-VENET-OUT"); ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -j PVEFW-VENET-OUT"); ruleset_create_chain($ruleset, "PVEFW-FWBR-IN"); - ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options); + ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options, $cluster_conf, $loglevel); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-in link+ -j PVEFW-FWBR-IN"); + ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-in fwln+ -j PVEFW-FWBR-IN"); ruleset_create_chain($ruleset, "PVEFW-FWBR-OUT"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-out link+ -j PVEFW-FWBR-OUT"); + ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-out fwln+ -j PVEFW-FWBR-OUT"); ruleset_create_chain($ruleset, "PVEFW-VENET-IN"); - ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options); + ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options, $cluster_conf, $loglevel); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -j PVEFW-VENET-IN"); @@ -2618,7 +2644,7 @@ sub compile { foreach my $netid (keys %$conf) { next if $netid !~ m/^net(\d+)$/; my $net = PVE::QemuServer::parse_net($conf->{$netid}); - next if !$net; + next if !$net->{firewall}; my $iface = "tap${vmid}i$1"; my $macaddr = $net->{macaddr}; @@ -2639,6 +2665,7 @@ sub compile { if ($conf->{ip_address} && $conf->{ip_address}->{value}) { my $ip = $conf->{ip_address}->{value}; + $ip =~ s/\s+/,/g; generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'IN'); generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'OUT'); } @@ -2781,7 +2808,7 @@ sub get_ipset_cmdlist { my $active_chains = ipset_get_chains(); my $statushash = get_ruleset_status($ruleset, $active_chains, \&ipset_chain_digest, $verbose); - # remove stale _swap chains + # remove stale _swap chains foreach my $chain (keys %$active_chains) { if ($chain =~ m/^PVEFW-\S+_swap$/) { $cmdlist .= "destroy $chain\n";