pass $rule_env (cluster/host/vm/ct) to rule parser.
authorDietmar Maurer <dietmar@proxmox.com>
Mon, 26 May 2014 06:09:02 +0000 (08:09 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 26 May 2014 06:09:02 +0000 (08:09 +0200)
So that we can correctly verify 'iface' parameter.

Also add new API classes for CTs (because we need to pass $rule_env).

src/PVE/API2/Firewall/Aliases.pm
src/PVE/API2/Firewall/Rules.pm
src/PVE/API2/Firewall/VM.pm
src/PVE/Firewall.pm

index 736ad62..1a637de 100644 (file)
@@ -324,7 +324,38 @@ __PACKAGE__->additional_parameters({
 sub load_config {
     my ($class, $param) = @_;
 
-    my $fw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
+    my $fw_conf = PVE::Firewall::load_vmfw_conf('vm', $param->{vmid});
+    my $aliases = $fw_conf->{aliases};
+
+    return ($fw_conf, $aliases);
+}
+
+sub save_aliases {
+    my ($class, $param, $fw_conf, $aliases) = @_;
+
+    $fw_conf->{aliases} = $aliases;
+    PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
+}
+
+__PACKAGE__->register_handlers();
+
+package PVE::API2::Firewall::CTAliases;
+
+use strict;
+use warnings;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::API2::Firewall::AliasesBase);
+
+__PACKAGE__->additional_parameters({ 
+    node => get_standard_option('pve-node'),
+    vmid => get_standard_option('pve-vmid'),                              
+});
+
+sub load_config {
+    my ($class, $param) = @_;
+
+    my $fw_conf = PVE::Firewall::load_vmfw_conf('ct', $param->{vmid});
     my $aliases = $fw_conf->{aliases};
 
     return ($fw_conf, $aliases);
index 63c4478..56b5331 100644 (file)
@@ -33,8 +33,10 @@ sub save_rules {
 
 my $additional_param_hash = {};
 
-sub allow_groups {
-    return 1;
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    die "implement this in subclass";
 }
 
 sub additional_parameters {
@@ -161,7 +163,7 @@ sub register_create_rule {
            my $rule = {};
 
            PVE::Firewall::copy_rule_data($rule, $param);
-           PVE::Firewall::verify_rule($rule, $class->allow_groups());
+           PVE::Firewall::verify_rule($rule, $class->rule_env());
 
            $rule->{enable} = 0 if !defined($param->{enable});
 
@@ -235,7 +237,7 @@ sub register_update_rule {
                
                PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
 
-               PVE::Firewall::verify_rule($rule, $class->allow_groups());
+               PVE::Firewall::verify_rule($rule, $class->rule_env());
            }
 
            $class->save_rules($param, $fw_conf, $rules);
@@ -302,8 +304,10 @@ use base qw(PVE::API2::Firewall::RulesBase);
 
 __PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
 
-sub allow_groups {
-    return 0;
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    return 'group';
 }
 
 sub load_config {
@@ -332,6 +336,12 @@ use warnings;
 
 use base qw(PVE::API2::Firewall::RulesBase);
 
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    return 'cluster';
+}
+
 sub load_config {
     my ($class, $param) = @_;
 
@@ -360,6 +370,12 @@ use base qw(PVE::API2::Firewall::RulesBase);
 
 __PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
 
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    return 'host';
+}
+
 sub load_config {
     my ($class, $param) = @_;
 
@@ -391,10 +407,53 @@ __PACKAGE__->additional_parameters({
     vmid => get_standard_option('pve-vmid'),                              
 });
 
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    return 'vm';
+}
+
+sub load_config {
+    my ($class, $param) = @_;
+
+    my $fw_conf = PVE::Firewall::load_vmfw_conf('vm', $param->{vmid});
+    my $rules = $fw_conf->{rules};
+
+    return ($fw_conf, $rules);
+}
+
+sub save_rules {
+    my ($class, $param, $fw_conf, $rules) = @_;
+
+    $fw_conf->{rules} = $rules;
+    PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
+}
+
+__PACKAGE__->register_handlers();
+
+package PVE::API2::Firewall::CTRules;
+
+use strict;
+use warnings;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::API2::Firewall::RulesBase);
+
+__PACKAGE__->additional_parameters({ 
+    node => get_standard_option('pve-node'),
+    vmid => get_standard_option('pve-vmid'),                              
+});
+
+sub rule_env {
+    my ($class, $param) = @_;
+    
+    return 'ct';
+}
+
 sub load_config {
     my ($class, $param) = @_;
 
-    my $fw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
+    my $fw_conf = PVE::Firewall::load_vmfw_conf('ct', $param->{vmid});
     my $rules = $fw_conf->{rules};
 
     return ($fw_conf, $rules);
index b38ca24..e0c7832 100644 (file)
@@ -1,4 +1,4 @@
-package PVE::API2::Firewall::VM;
+package PVE::API2::Firewall::VMBase;
 
 use strict;
 use warnings;
@@ -12,48 +12,6 @@ use Data::Dumper; # fixme: remove
 
 use base qw(PVE::RESTHandler);
 
-__PACKAGE__->register_method ({
-    subclass => "PVE::API2::Firewall::VMRules",  
-    path => 'rules',
-});
-
-__PACKAGE__->register_method ({
-    subclass => "PVE::API2::Firewall::VMAliases",  
-    path => 'aliases',
-});
-
-__PACKAGE__->register_method({
-    name => 'index',
-    path => '',
-    method => 'GET',
-    permissions => { user => 'all' },
-    description => "Directory index.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           vmid => get_standard_option('pve-vmid'),
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {},
-       },
-       links => [ { rel => 'child', href => "{name}" } ],
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $result = [
-           { name => 'rules' },
-           { name => 'options' },
-           ];
-
-       return $result;
-    }});
-
 my $option_properties = {
     enable => {
        description => "Enable host firewall rules.",
@@ -98,140 +56,217 @@ my $add_option_properties = sub {
     
     return $properties;
 };
-__PACKAGE__->register_method({
-    name => 'get_options',
-    path => 'options',
-    method => 'GET',
-    description => "Get VM firewall options.",
-    proxyto => 'node',
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           vmid => get_standard_option('pve-vmid'),
+
+sub register_handlers {
+    my ($class, $rule_env) = @_;
+
+    $class->register_method({
+       name => 'index',
+       path => '',
+       method => 'GET',
+       permissions => { user => 'all' },
+       description => "Directory index.",
+       parameters => {
+           additionalProperties => 0,
+           properties => {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+           },
        },
-    },
-    returns => {
-       type => "object",
-       #additionalProperties => 1,
-       properties => $option_properties,
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $vmfw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
-
-       return PVE::Firewall::copy_opject_with_digest($vmfw_conf->{options});
-    }});
-
-__PACKAGE__->register_method({
-    name => 'set_options',
-    path => 'options',
-    method => 'PUT',
-    description => "Set Firewall options.",
-    protected => 1,
-    proxyto => 'node',
-    parameters => {
-       additionalProperties => 0,
-       properties => &$add_option_properties({
-           node => get_standard_option('pve-node'),
-           vmid => get_standard_option('pve-vmid'),
-           delete => {
-               type => 'string', format => 'pve-configid-list',
-               description => "A list of settings you want to delete.",
-               optional => 1,
+       returns => {
+           type => 'array',
+           items => {
+               type => "object",
+               properties => {},
            },
-           digest => get_standard_option('pve-config-digest'),
-       }),
-    },
-    returns => { type => "null" },
-    code => sub {
-       my ($param) = @_;
+           links => [ { rel => 'child', href => "{name}" } ],
+       },
+       code => sub {
+           my ($param) = @_;
 
-       my $vmfw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
+           my $result = [
+               { name => 'rules' },
+               { name => 'aliases' },
+               { name => 'options' },
+               ];
 
-       my (undef, $digest) = PVE::Firewall::copy_opject_with_digest($vmfw_conf->{options});
-       PVE::Tools::assert_if_modified($digest, $param->{digest});
+           return $result;
+       }});
 
-       if ($param->{delete}) {
-           foreach my $opt (PVE::Tools::split_list($param->{delete})) {
-               raise_param_exc({ delete => "no such option '$opt'" }) 
-                   if !$option_properties->{$opt};
-               delete $vmfw_conf->{options}->{$opt};
-           }
-       }
-
-       if (defined($param->{enable})) {
-           $param->{enable} = $param->{enable} ? 1 : 0;
-       }
-
-       foreach my $k (keys %$option_properties) {
-           next if !defined($param->{$k});
-           $vmfw_conf->{options}->{$k} = $param->{$k}; 
-       }
-
-       PVE::Firewall::save_vmfw_conf($param->{vmid}, $vmfw_conf);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method({
-    name => 'log', 
-    path => 'log', 
-    method => 'GET',
-    description => "Read firewall log",
-    proxyto => 'node',
-    permissions => {
-       check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
-    },
-    protected => 1,
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           vmid => get_standard_option('pve-vmid'),
-           start => {
-               type => 'integer',
-               minimum => 0,
-               optional => 1,
-           },
-           limit => {
-               type => 'integer',
-               minimum => 0,
-               optional => 1,
+
+    $class->register_method({
+       name => 'get_options',
+       path => 'options',
+       method => 'GET',
+       description => "Get VM firewall options.",
+       proxyto => 'node',
+       parameters => {
+           additionalProperties => 0,
+           properties => {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
            },
        },
-    },
-    returns => {
-       type => 'array',
-       items => { 
+       returns => {
            type => "object",
-           properties => {
-               n => {
-                 description=>  "Line number",
-                 type=> 'integer',
+           #additionalProperties => 1,
+           properties => $option_properties,
+       },
+       code => sub {
+           my ($param) = @_;
+
+           my $vmfw_conf = PVE::Firewall::load_vmfw_conf($rule_env, $param->{vmid});
+
+           return PVE::Firewall::copy_opject_with_digest($vmfw_conf->{options});
+       }});
+
+    $class->register_method({
+       name => 'set_options',
+       path => 'options',
+       method => 'PUT',
+       description => "Set Firewall options.",
+       protected => 1,
+       proxyto => 'node',
+       parameters => {
+           additionalProperties => 0,
+           properties => &$add_option_properties({
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               delete => {
+                   type => 'string', format => 'pve-configid-list',
+                   description => "A list of settings you want to delete.",
+                   optional => 1,
                },
-               t => {
-                 description=>  "Line text",
-                 type => 'string',
+               digest => get_standard_option('pve-config-digest'),
+           }),
+       },
+       returns => { type => "null" },
+       code => sub {
+           my ($param) = @_;
+
+           my $vmfw_conf = PVE::Firewall::load_vmfw_conf($rule_env, $param->{vmid});
+
+           my (undef, $digest) = PVE::Firewall::copy_opject_with_digest($vmfw_conf->{options});
+           PVE::Tools::assert_if_modified($digest, $param->{digest});
+
+           if ($param->{delete}) {
+               foreach my $opt (PVE::Tools::split_list($param->{delete})) {
+                   raise_param_exc({ delete => "no such option '$opt'" }) 
+                       if !$option_properties->{$opt};
+                   delete $vmfw_conf->{options}->{$opt};
                }
            }
-       }
-    },
-    code => sub {
-       my ($param) = @_;
 
-       my $rpcenv = PVE::RPCEnvironment::get();
-       my $user = $rpcenv->get_user();
-       my $vmid = $param->{vmid};
+           if (defined($param->{enable})) {
+               $param->{enable} = $param->{enable} ? 1 : 0;
+           }
+
+           foreach my $k (keys %$option_properties) {
+               next if !defined($param->{$k});
+               $vmfw_conf->{options}->{$k} = $param->{$k}; 
+           }
+
+           PVE::Firewall::save_vmfw_conf($param->{vmid}, $vmfw_conf);
+           
+           return undef;
+       }});
 
-       my ($count, $lines) = PVE::Tools::dump_logfile("/var/log/pve-firewall.log", 
-                                                      $param->{start}, $param->{limit},
-                                                      "^$vmid ");
+    $class->register_method({
+       name => 'log', 
+       path => 'log', 
+       method => 'GET',
+       description => "Read firewall log",
+       proxyto => 'node',
+       permissions => {
+           check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
+       },
+       protected => 1,
+       parameters => {
+           additionalProperties => 0,
+           properties => {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               start => {
+                   type => 'integer',
+                   minimum => 0,
+                   optional => 1,
+               },
+               limit => {
+                   type => 'integer',
+                   minimum => 0,
+                   optional => 1,
+               },
+           },
+       },
+       returns => {
+           type => 'array',
+           items => { 
+               type => "object",
+               properties => {
+                   n => {
+                       description=>  "Line number",
+                       type=> 'integer',
+                   },
+                   t => {
+                       description=>  "Line text",
+                       type => 'string',
+                   }
+               }
+           }
+       },
+       code => sub {
+           my ($param) = @_;
 
-       $rpcenv->set_result_attrib('total', $count);
+           my $rpcenv = PVE::RPCEnvironment::get();
+           my $user = $rpcenv->get_user();
+           my $vmid = $param->{vmid};
+
+           my ($count, $lines) = PVE::Tools::dump_logfile("/var/log/pve-firewall.log", 
+                                                          $param->{start}, $param->{limit},
+                                                          "^$vmid ");
+           
+           $rpcenv->set_result_attrib('total', $count);
            
-       return $lines; 
-    }});
+           return $lines; 
+       }});
+}
+
+package PVE::API2::Firewall::VM;
+
+use strict;
+use warnings;
+
+use base qw(PVE::API2::Firewall::VMBase);
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::VMRules",  
+    path => 'rules',
+});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::VMAliases",  
+    path => 'aliases',
+});
+
+__PACKAGE__->register_handlers('vm');
+
+package PVE::API2::Firewall::CT;
+
+use strict;
+use warnings;
+
+use base qw(PVE::API2::Firewall::VMBase);
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::CTRules",  
+    path => 'rules',
+});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::CTAliases",  
+    path => 'aliases',
+});
+
+__PACKAGE__->register_handlers('vm');
 
 1;
index 5384cbe..deef1ae 100644 (file)
@@ -1004,8 +1004,21 @@ my $apply_macro = sub {
     return $rules;
 };
 
+my $rule_env_iface_lookup = {
+    'ct' => 1,
+    'vm' => 1,
+    'group' => 0,
+    'cluster' => 1,
+    'host' => 1,
+};
+
 sub verify_rule {
-    my ($rule, $allow_groups, $noerr) = @_;
+    my ($rule, $rule_env, $noerr) = @_;
+
+    my $allow_groups = $rule_env eq 'group' ? 0 : 1;
+    
+    my $allow_iface = $rule_env_iface_lookup->{$rule_env};
+    die "unknown rule_env '$rule_env'\n" if !defined($allow_iface); # should not happen
 
     my $errors = {};
     my $error_count = 0;
@@ -1039,9 +1052,18 @@ sub verify_rule {
     }
 
     if ($rule->{iface}) {
+       &$add_error('type', "parameter -i not allowed for this rule type") 
+           if !$allow_iface;
        eval { PVE::JSONSchema::pve_verify_iface($rule->{iface}); };
        &$add_error('iface', $@) if $@;
-    }  
+       if ($rule_env eq 'vm') {
+           &$add_error('iface', "value does not match the regex pattern 'net\\d+'")
+               if $rule->{iface} !~  m/^net(\d+)$/;
+       } elsif ($rule_env eq 'ct') {
+           &$add_error('iface', "value does not match the regex pattern '(venet|eth\\d+)'")
+               if $rule->{iface} !~  m/^(venet|eth(\d+))$/;
+       }
+    }
 
     if ($rule->{macro}) {
        if (my $preferred_name = $pve_fw_preferred_macro_names->{lc($rule->{macro})}) {
@@ -1853,7 +1875,7 @@ for (my $i = 0; $i < $MAX_NETS; $i++)  {
 }
 
 sub parse_fw_rule {
-    my ($prefix, $line, $allow_iface, $allow_groups, $verbose) = @_;
+    my ($prefix, $line, $rule_env, $verbose) = @_;
 
     chomp $line;
 
@@ -1885,7 +1907,6 @@ sub parse_fw_rule {
 
     while (length($line)) {
        if ($line =~ s/^-i (\S+)\s*//) {
-           die "parameter -i not allowed\n" if !$allow_iface;
            $rule->{iface} = $1;
            next;
        }
@@ -1920,7 +1941,7 @@ sub parse_fw_rule {
 
     die "unable to parse rule parameters: $line\n" if length($line);
 
-    $rule = verify_rule($rule, $allow_groups, 1);
+    $rule = verify_rule($rule, $rule_env, 1);
     if ($verbose && $rule->{errors}) {
        warn "$prefix - errors in rule parameters: $orig_line\n";
        foreach my $p (keys %{$rule->{errors}}) {
@@ -2023,7 +2044,7 @@ sub parse_alias {
 }
 
 sub parse_vm_fw_rules {
-    my ($filename, $fh, $verbose) = @_;
+    my ($filename, $fh, $rule_env, $verbose) = @_;
 
     my $res = { 
        rules => [], 
@@ -2071,7 +2092,7 @@ sub parse_vm_fw_rules {
        }
 
        my $rule;
-       eval { $rule = parse_fw_rule($prefix, $line, 1, 1, $verbose); };
+       eval { $rule = parse_fw_rule($prefix, $line, $rule_env, $verbose); };
        if (my $err = $@) {
            warn "$prefix: $err";
            next;
@@ -2119,7 +2140,7 @@ sub parse_host_fw_rules {
        }
 
        my $rule;
-       eval { $rule = parse_fw_rule($prefix, $line, 1, 1, $verbose); };
+       eval { $rule = parse_fw_rule($prefix, $line, 'host', $verbose); };
        if (my $err = $@) {
            warn "$prefix: $err";
            next;
@@ -2208,7 +2229,7 @@ sub parse_cluster_fw_rules {
            warn "$prefix: $@" if $@;
        } elsif ($section eq 'rules') {
            my $rule;
-           eval { $rule = parse_fw_rule($prefix, $line, 1, 1, $verbose); };
+           eval { $rule = parse_fw_rule($prefix, $line, 'cluster', $verbose); };
            if (my $err = $@) {
                warn "$prefix: $err";
                next;
@@ -2216,7 +2237,7 @@ sub parse_cluster_fw_rules {
            push @{$res->{$section}}, $rule;
        } elsif ($section eq 'groups') {
            my $rule;
-           eval { $rule = parse_fw_rule($prefix, $line, 0, 0, $verbose); };
+           eval { $rule = parse_fw_rule($prefix, $line, 'group', $verbose); };
            if (my $err = $@) {
                warn "$prefix: $err";
                next;
@@ -2300,7 +2321,7 @@ sub read_local_vm_config {
 };
 
 sub load_vmfw_conf {
-    my ($vmid, $dir, $verbose) = @_;
+    my ($rule_env, $vmid, $dir, $verbose) = @_;
 
     my $vmfw_conf = {};
 
@@ -2308,7 +2329,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, $verbose);
+       $vmfw_conf = parse_vm_fw_rules($filename, $fh, $rule_env, $verbose);
     }
 
     return $vmfw_conf;
@@ -2432,8 +2453,13 @@ sub read_vm_firewall_configs {
 
     my $vmfw_configs = {};
 
-    foreach my $vmid (keys %{$vmdata->{qemu}}, keys %{$vmdata->{openvz}}) {
-       my $vmfw_conf = load_vmfw_conf($vmid, $dir, $verbose);
+    foreach my $vmid (keys %{$vmdata->{qemu}}) {
+       my $vmfw_conf = load_vmfw_conf('vm', $vmid, $dir, $verbose);
+       next if !$vmfw_conf->{options}; # skip if file does not exists
+       $vmfw_configs->{$vmid} = $vmfw_conf;
+    }
+    foreach my $vmid (keys %{$vmdata->{openvz}}) {
+       my $vmfw_conf = load_vmfw_conf('ct', $vmid, $dir, $verbose);
        next if !$vmfw_conf->{options}; # skip if file does not exists
        $vmfw_configs->{$vmid} = $vmfw_conf;
     }