X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=93e53f5bed2374217515a857fc9770718268e6cc;hb=649e4d579b2b4d30fe6a17b4234cf440181cb5b6;hp=5168093d9fca75ff0cd42d9d46865ff984449fa7;hpb=6e299ae385402df8019ecf34db56f05a0e9ffeae;p=pve-firewall.git diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 5168093..93e53f5 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -7,10 +7,10 @@ use Data::Dumper; use Digest::SHA; use PVE::INotify; use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); +use PVE::JSONSchema qw(register_standard_option get_standard_option); use PVE::Cluster; use PVE::ProcFSTools; -use PVE::Tools; +use PVE::Tools qw($IPV4RE); use File::Basename; use File::Path; use IO::File; @@ -35,6 +35,37 @@ eval { $have_pve_manager = 1; }; +PVE::JSONSchema::register_format('IPv4orCIDR', \&pve_verify_ipv4_or_cidr); +sub pve_verify_ipv4_or_cidr { + my ($cidr, $noerr) = @_; + + if ($cidr =~ m!^(?:$IPV4RE)(/(\d+))?$!) { + return $cidr if Net::IP->new($cidr); + return undef if $noerr; + die Net::IP::Error() . "\n"; + } + return undef if $noerr; + die "value does not look like a valid IP address or CIDR network\n"; +} + +PVE::JSONSchema::register_standard_option('ipset-name', { + description => "IP set name.", + type => 'string', + pattern => '[A-Za-z][A-Za-z0-9\-\_]+', + minLength => 2, + maxLength => 20, +}); + +my $security_group_pattern = '[A-Za-z][A-Za-z0-9\-\_]+'; + +PVE::JSONSchema::register_standard_option('pve-security-group-name', { + description => "Security Group name.", + type => 'string', + pattern => $security_group_pattern, + minLength => 2, + maxLength => 20, +}); + my $feature_ipset_nomatch = 0; eval { my (undef, undef, $release) = POSIX::uname(); @@ -673,7 +704,7 @@ sub get_etc_protocols { sub parse_address_list { my ($str) = @_; - return if $str !~ m/^(\+)(\S+)$/; # ipset ref + return if $str =~ m/^(\+)(\S+)$/; # ipset ref my $count = 0; my $iprange = 0; @@ -760,21 +791,19 @@ my $rule_properties = { minimum => 0, optional => 1, }, - digest => { - type => 'string', - optional => 1, - maxLength => 27, - minLength => 27, - }, + digest => get_standard_option('pve-config-digest'), type => { type => 'string', optional => 1, enum => ['in', 'out', 'group'], }, action => { + description => "Rule action ('ACCEPT', 'DROP', 'REJECT') or security group name.", type => 'string', optional => 1, - enum => ['ACCEPT', 'DROP', 'REJECT'], + pattern => $security_group_pattern, + maxLength => 20, + minLength => 2, }, macro => { type => 'string', @@ -921,7 +950,7 @@ sub verify_rule { raise_param_exc({ type => "security groups not allowed"}) if !$allow_groups; raise_param_exc({ action => "invalid characters in security group name"}) - if $rule->{action} !~ m/^[A-Za-z0-9_\-]+$/; + if $rule->{action} !~ m/^${security_group_pattern}$/; } else { raise_param_exc({ type => "unknown rule type '$type'"}); } @@ -1719,7 +1748,7 @@ sub parse_fw_rule { 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/^[A-Za-z0-9_\-]+$/; + die "invalid characters in group name\n" if $action !~ m/^${security_group_pattern}$/; } else { die "unknown rule type '$type'\n"; } @@ -1931,7 +1960,7 @@ sub parse_host_fw_rules { push @{$res->{$section}}, $rule; } -$res->{digest} = $digest->b64digest; + $res->{digest} = $digest->b64digest; return $res; } @@ -1942,7 +1971,14 @@ sub parse_cluster_fw_rules { my $section; my $group; - my $res = { rules => [], options => {}, groups => {}, ipset => {} }; + my $res = { + rules => [], + options => {}, + groups => {}, + group_comments => {}, + ipset => {} , + ipset_comments => {}, + }; my $digest = Digest::SHA->new('sha1'); @@ -1960,9 +1996,13 @@ sub parse_cluster_fw_rules { next; } - if ($line =~ m/^\[group\s+(\S+)\]\s*$/i) { + if ($line =~ m/^\[group\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) { $section = 'groups'; $group = lc($1); + my $comment = $2; + $res->{$section}->{$group} = []; + $res->{group_comments}->{$group} = decode('utf8', $comment) + if $comment; next; } @@ -1971,9 +2011,13 @@ sub parse_cluster_fw_rules { next; } - if ($line =~ m/^\[ipset\s+(\S+)\]\s*$/i) { + if ($line =~ m/^\[ipset\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) { $section = 'ipset'; $group = lc($1); + my $comment = $2; + $res->{$section}->{$group} = []; + $res->{ipset_comments}->{$group} = decode('utf8', $comment) + if $comment; next; } @@ -2008,15 +2052,15 @@ sub parse_cluster_fw_rules { # we can add single line comments to the end of the rule my $comment = decode('utf8', $1) if $line =~ s/#\s*(.*?)\s*$//; - $line =~ m/^(\!)?\s*((?:\d+)\.(?:\d+)\.(?:\d+)\.(?:\d+))(?:\/(\d+))?\s*$/; + $line =~ m/^(\!)?\s*(\S+)\s*$/; my $nomatch = $1; my $cidr = $2; - $cidr .= "/$3" if defined($3) && $3 != 32; - - if (!Net::IP->new($cidr)) { - my $err = Net::IP::Error(); - warn "$prefix: $cidr - $err\n"; + $cidr =~ s|/32$||; + + eval { pve_verify_ipv4_or_cidr($cidr); }; + if (my $err = $@) { + warn "$prefix: $cidr - $err"; next; } @@ -2099,7 +2143,7 @@ my $format_rules = sub { my $raw = ''; foreach my $rule (@$rules) { - if ($rule->{type} eq 'in' || $rule->{type} eq 'out') { + if ($rule->{type} eq 'in' || $rule->{type} eq 'out' || $rule->{type} eq 'group') { $raw .= '|' if defined($rule->{enable}) && !$rule->{enable}; $raw .= uc($rule->{type}); if ($rule->{macro}) { @@ -2108,16 +2152,20 @@ my $format_rules = sub { $raw .= " " . $rule->{action}; } $raw .= " " . ($rule->{iface} || '-') if $need_iface; - $raw .= " " . ($rule->{source} || '-'); - $raw .= " " . ($rule->{dest} || '-'); - $raw .= " " . ($rule->{proto} || '-'); - $raw .= " " . ($rule->{dport} || '-'); - $raw .= " " . ($rule->{sport} || '-'); + + if ($rule->{type} ne 'group') { + $raw .= " " . ($rule->{source} || '-'); + $raw .= " " . ($rule->{dest} || '-'); + $raw .= " " . ($rule->{proto} || '-'); + $raw .= " " . ($rule->{dport} || '-'); + $raw .= " " . ($rule->{sport} || '-'); + } + $raw .= " # " . encode('utf8', $rule->{comment}) if $rule->{comment} && $rule->{comment} !~ m/^\s*$/; $raw .= "\n"; } else { - die "implement me '$rule->{type}'"; + die "unknown rule type '$rule->{type}'"; } } @@ -2259,8 +2307,15 @@ sub generate_ipset { push @{$ipset_ruleset->{$name}}, "create $name hash:net family inet hashsize $hashsize maxelem $hashsize"; + # remove duplicates + my $nethash = {}; foreach my $entry (@$options) { - my $cidr = $entry->{cidr}; + $nethash->{$entry->{cidr}} = $entry; + } + + foreach my $cidr (sort keys %$nethash) { + my $entry = $nethash->{$cidr}; + my $cmd = "add $name $cidr"; if ($entry->{nomatch}) { if ($feature_ipset_nomatch) { @@ -2353,7 +2408,12 @@ sub save_clusterfw_conf { $raw .= &$format_options($options) if scalar(keys %$options); foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) { - $raw .= "[IPSET $ipset]\n\n"; + if (my $comment = $cluster_conf->{ipset_comments}->{$ipset}) { + my $utf8comment = encode('utf8', $comment); + $raw .= "[IPSET $ipset] # $utf8comment\n\n"; + } else { + $raw .= "[IPSET $ipset]\n\n"; + } my $options = $cluster_conf->{ipset}->{$ipset}; $raw .= &$format_ipset($options); $raw .= "\n"; @@ -2368,7 +2428,13 @@ sub save_clusterfw_conf { foreach my $group (sort keys %{$cluster_conf->{groups}}) { my $rules = $cluster_conf->{groups}->{$group}; - $raw .= "[group $group]\n\n"; + if (my $comment = $cluster_conf->{group_comments}->{$group}) { + my $utf8comment = encode('utf8', $comment); + $raw .= "[group $group] # $utf8comment\n\n"; + } else { + $raw .= "[group $group]\n\n"; + } + $raw .= &$format_rules($rules, 0); $raw .= "\n"; } @@ -2636,6 +2702,13 @@ 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 + foreach my $chain (keys %$active_chains) { + if ($chain =~ m/^PVEFW-\S+_swap$/) { + $cmdlist .= "destroy $chain\n"; + } + } + foreach my $chain (sort keys %$ruleset) { my $stat = $statushash->{$chain}; die "internal error" if !$stat;