]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
skip non-existent aliases inside ipset configuration
[pve-firewall.git] / src / PVE / Firewall.pm
index 3b6b24588274829e639e314cdb9cebacb19e1afb..8407e3cc8b4b344980f97837380969e9b0af829e 100644 (file)
@@ -35,6 +35,14 @@ eval {
     $have_pve_manager = 1;
 };
 
+my $security_group_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $ipset_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $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;
+
 PVE::JSONSchema::register_format('IPv4orCIDR', \&pve_verify_ipv4_or_cidr);
 sub pve_verify_ipv4_or_cidr {
     my ($cidr, $noerr) = @_;
@@ -48,20 +56,35 @@ sub pve_verify_ipv4_or_cidr {
     die "value does not look like a valid IP address or CIDR network\n";
 }
 
+PVE::JSONSchema::register_format('IPv4orCIDRorAlias', \&pve_verify_ipv4_or_cidr_or_alias);
+sub pve_verify_ipv4_or_cidr_or_alias {
+    my ($cidr, $noerr) = @_;
+
+    return if $cidr =~ m/^(?:$ip_alias_pattern)$/;
+
+    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\-\_]+',
+    pattern => $ipset_name_pattern,
     minLength => 2,
-    maxLength => 20,
+    maxLength => $max_ipset_name_length,
 });
 
 PVE::JSONSchema::register_standard_option('pve-fw-alias', {
     description => "Alias name.",
     type => 'string',
-    pattern => '[A-Za-z][A-Za-z0-9\-\_]+',
+    pattern => $ip_alias_pattern,
     minLength => 2,
-    maxLength => 20,
+    maxLength => $max_alias_name_length,
 });
 
 PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => {
@@ -71,19 +94,12 @@ PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => {
     optional => 1,
 });
 
-my $security_group_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
-my $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
-
-my $max_alias_name_length = 64;
-my $max_ipset_name_length = 64;
-
-
 PVE::JSONSchema::register_standard_option('pve-security-group-name', {
     description => "Security Group name.",
     type => 'string',
     pattern => $security_group_name_pattern,
     minLength => 2,
-    maxLength => 20,
+    maxLength => $max_group_name_length,
 });
 
 my $feature_ipset_nomatch = 0;
@@ -1065,12 +1081,12 @@ sub verify_rule {
 
        if (my $value = $rule->{$name}) {
            if ($value =~ m/^\+/) {
-               if ($value =~ m/^\+(${security_group_name_pattern})$/) {
+               if ($value =~ m/^\+(${ipset_name_pattern})$/) {
                    &$add_error($name, "no such ipset '$1'")
                        if !($cluster_conf->{ipset}->{$1} || ($fw_conf && $fw_conf->{ipset}->{$1}));
 
                } else {
-                   &$add_error($name, "invalid security group name '$value'");
+                   &$add_error($name, "invalid ipset name '$value'");
                }
            } elsif ($value =~ m/^${ip_alias_pattern}$/){
                my $alias = lc($value);
@@ -1348,7 +1364,7 @@ sub ruleset_generate_cmdstr {
 
     if ($source) {
         if ($source =~ m/^\+/) {
-           if ($source =~ m/^\+(${security_group_name_pattern})$/) {
+           if ($source =~ m/^\+(${ipset_name_pattern})$/) {
                my $name = $1;
                if ($fw_conf && $fw_conf->{ipset}->{$name}) {
                    my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name);
@@ -1377,7 +1393,7 @@ sub ruleset_generate_cmdstr {
 
     if ($dest) {
         if ($dest =~ m/^\+/) {
-           if ($dest =~ m/^\+(${security_group_name_pattern})$/) {
+           if ($dest =~ m/^\+(${ipset_name_pattern})$/) {
                my $name = $1;
                if ($fw_conf && $fw_conf->{ipset}->{$name}) {
                    my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name);
@@ -2100,6 +2116,21 @@ sub parse_clusterfw_option {
     return ($opt, $value);
 }
 
+sub resolve_alias {
+    my ($clusterfw_conf, $fw_conf, $cidr) = @_;
+
+    if ($cidr !~ m/^\d/) {
+       my $alias = lc($cidr);
+       my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+       $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf;
+       return $e->{cidr} if $e;
+       
+       die "no such alias '$cidr'\n";
+    }
+
+    return $cidr;
+}
+
 sub parse_alias {
     my ($line) = @_;
 
@@ -2121,7 +2152,7 @@ sub parse_alias {
     return undef;
 }
 
-sub generic_fw_rules_parser {
+sub generic_fw_config_parser {
     my ($filename, $fh, $verbose, $cluster_conf, $empty_conf, $rule_env) = @_;
 
     my $section;
@@ -2150,6 +2181,16 @@ sub generic_fw_rules_parser {
            $section = 'groups';
            $group = lc($1);
            my $comment = $2;
+           eval {
+               die "security group name too long\n" if length($group) > $max_group_name_length;
+               die "invalid security group name '$group'\n" if $group !~ m/^${security_group_name_pattern}$/;
+           };
+           if (my $err = $@) {
+               ($section, $group, $comment) = undef;
+               warn "$prefix: $err";
+               next;
+           }
+           
            $res->{$section}->{$group} = [];
            $res->{group_comments}->{$group} =  decode('utf8', $comment)
                if $comment;
@@ -2165,6 +2206,16 @@ sub generic_fw_rules_parser {
            $section = 'ipset';
            $group = lc($1);
            my $comment = $2;
+           eval {      
+               die "ipset name too long\n" if length($group) > $max_ipset_name_length;
+               die "invalid ipset name '$group'\n" if $group !~ m/^${ipset_name_pattern}$/;
+           };
+           if (my $err = $@) {
+               ($section, $group, $comment) = undef;
+               warn "$prefix: $err";
+               next;
+           }
+
            $res->{$section}->{$group} = [];
            $res->{ipset_comments}->{$group} = decode('utf8', $comment)
                if $comment;
@@ -2219,14 +2270,17 @@ sub generic_fw_rules_parser {
            my $nomatch = $1;
            my $cidr = $2;
 
-           if($cidr !~ m/^${ip_alias_pattern}$/) {
-               $cidr =~ s|/32$||;
-
-               eval { pve_verify_ipv4_or_cidr($cidr); };
-               if (my $err = $@) {
-                   warn "$prefix: $cidr - $err";
-                   next;
+           eval { 
+               if ($cidr =~ m/^${ip_alias_pattern}$/) {
+                   resolve_alias($cluster_conf, $res, $cidr); # make sure alias exists
+               } else {
+                   $cidr =~ s|/32$||;
+                   pve_verify_ipv4_or_cidr($cidr);
                }
+           };
+           if (my $err = $@) {
+               warn "$prefix: $cidr - $err";
+               next;
            }
 
            my $entry = { cidr => $cidr };
@@ -2243,15 +2297,15 @@ sub generic_fw_rules_parser {
     return $res;
 }
 
-sub parse_host_fw_rules {
+sub parse_hostfw_config {
     my ($filename, $fh, $cluster_conf, $verbose) = @_;
 
     my $empty_conf = { rules => [], options => {}};
 
-    return generic_fw_rules_parser($filename, $fh, $verbose, $cluster_conf, $empty_conf, 'host');
+    return generic_fw_config_parser($filename, $fh, $verbose, $cluster_conf, $empty_conf, 'host');
 }
 
-sub parse_vm_fw_rules {
+sub parse_vmfw_config {
     my ($filename, $fh, $cluster_conf, $rule_env, $verbose) = @_;
 
     my $empty_conf = {
@@ -2262,10 +2316,10 @@ sub parse_vm_fw_rules {
        ipset_comments => {},
     };
 
-    return generic_fw_rules_parser($filename, $fh, $verbose, $cluster_conf, $empty_conf, $rule_env);
+    return generic_fw_config_parser($filename, $fh, $verbose, $cluster_conf, $empty_conf, $rule_env);
 }
 
-sub parse_cluster_fw_rules {
+sub parse_clusterfw_config {
     my ($filename, $fh, $verbose) = @_;
 
     my $section;
@@ -2281,7 +2335,7 @@ sub parse_cluster_fw_rules {
        ipset_comments => {},
     };
 
-    return generic_fw_rules_parser($filename, $fh, $verbose, $empty_conf, $empty_conf, 'cluster');
+    return generic_fw_config_parser($filename, $fh, $verbose, $empty_conf, $empty_conf, 'cluster');
 }
 
 sub run_locked {
@@ -2341,7 +2395,7 @@ sub load_vmfw_conf {
 
     my $filename = "$dir/$vmid.fw";
     if (my $fh = IO::File->new($filename, O_RDONLY)) {
-       $vmfw_conf = parse_vm_fw_rules($filename, $fh, $cluster_conf, $rule_env, $verbose);
+       $vmfw_conf = parse_vmfw_config($filename, $fh, $cluster_conf, $rule_env, $verbose);
        $vmfw_conf->{vmid} = $vmid;
     }
 
@@ -2570,20 +2624,11 @@ sub generate_ipset {
     # remove duplicates
     my $nethash = {};
     foreach my $entry (@$options) {
-       my $cidr = $entry->{cidr};
-       if ($cidr =~ m/^${ip_alias_pattern}$/) {
-           my $alias = lc($cidr);
-           my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
-           $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf;
-           if ($e) {
-               $entry->{cidr} = $e->{cidr};
-               $nethash->{$entry->{cidr}} = $entry;
-           } else {
-               warn "no such alias '$cidr'\n";
-           }
-       } else {
-           $nethash->{$entry->{cidr}} = $entry;
-       }
+       eval {
+           my $cidr = resolve_alias($clusterfw_conf, $fw_conf, $entry->{cidr});
+           $nethash->{$cidr} = { cidr => $cidr, nomatch => $entry->{nomatch} };
+       };
+       warn $@ if $@;
     }
 
     foreach my $cidr (sort keys %$nethash) {
@@ -2617,7 +2662,7 @@ sub load_clusterfw_conf {
 
     my $cluster_conf = {};
     if (my $fh = IO::File->new($filename, O_RDONLY)) {
-       $cluster_conf = parse_cluster_fw_rules($filename, $fh, $verbose);
+       $cluster_conf = parse_clusterfw_config($filename, $fh, $verbose);
     }
 
     return $cluster_conf;
@@ -2666,7 +2711,7 @@ sub load_hostfw_conf {
 
     my $hostfw_conf = {};
     if (my $fh = IO::File->new($filename, O_RDONLY)) {
-       $hostfw_conf = parse_host_fw_rules($filename, $fh, $cluster_conf, $verbose);
+       $hostfw_conf = parse_hostfw_config($filename, $fh, $cluster_conf, $verbose);
     }
     return $hostfw_conf;
 }