From: Dietmar Maurer Date: Mon, 26 May 2014 06:09:02 +0000 (+0200) Subject: pass $rule_env (cluster/host/vm/ct) to rule parser. X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=commitdiff_plain;h=b6b8e6ade708f964c215ec94920025d07fce932f pass $rule_env (cluster/host/vm/ct) to rule parser. So that we can correctly verify 'iface' parameter. Also add new API classes for CTs (because we need to pass $rule_env). --- diff --git a/src/PVE/API2/Firewall/Aliases.pm b/src/PVE/API2/Firewall/Aliases.pm index 736ad62..1a637de 100644 --- a/src/PVE/API2/Firewall/Aliases.pm +++ b/src/PVE/API2/Firewall/Aliases.pm @@ -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); diff --git a/src/PVE/API2/Firewall/Rules.pm b/src/PVE/API2/Firewall/Rules.pm index 63c4478..56b5331 100644 --- a/src/PVE/API2/Firewall/Rules.pm +++ b/src/PVE/API2/Firewall/Rules.pm @@ -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); diff --git a/src/PVE/API2/Firewall/VM.pm b/src/PVE/API2/Firewall/VM.pm index b38ca24..e0c7832 100644 --- a/src/PVE/API2/Firewall/VM.pm +++ b/src/PVE/API2/Firewall/VM.pm @@ -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; diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 5384cbe..deef1ae 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -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; }