X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FFirewall%2FRules.pm;h=ec93dec2b1ba71e0a4afeba65e20e13e20c6890d;hp=4837880974041ee55e035fa0158161065e9a4a10;hb=7f733a5a9fb2a46d55905688aa4b1c80dd724bf3;hpb=387d0ffce61dcd0264b01165ccb4e4e197af7d76 diff --git a/src/PVE/API2/Firewall/Rules.pm b/src/PVE/API2/Firewall/Rules.pm index 4837880..ec93dec 100644 --- a/src/PVE/API2/Firewall/Rules.pm +++ b/src/PVE/API2/Firewall/Rules.pm @@ -3,6 +3,7 @@ package PVE::API2::Firewall::RulesBase; use strict; use warnings; use PVE::JSONSchema qw(get_standard_option); +use PVE::Exception qw(raise raise_param_exc); use PVE::Firewall; @@ -21,7 +22,7 @@ sub load_config { die "implement this in subclass"; - #return ($fw_conf, $rules); + #return ($cluster_conf, $fw_conf, $rules); } sub save_rules { @@ -32,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 { @@ -50,20 +53,64 @@ sub additional_parameters { return $copy; } +my $rules_modify_permissions = sub { + my ($rule_env) = @_; + + if ($rule_env eq 'host') { + return { + check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + }; + } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') { + return { + check => ['perm', '/', [ 'Sys.Modify' ]], + }; + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + return { + check => ['perm', '/vms/{vmid}', [ 'VM.Config.Network' ]], + } + } + + return undef; +}; + +my $rules_audit_permissions = sub { + my ($rule_env) = @_; + + if ($rule_env eq 'host') { + return { + check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + }; + } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') { + return { + check => ['perm', '/', [ 'Sys.Audit' ]], + }; + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + return { + check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], + } + } + + return undef; +}; + sub register_get_rules { my ($class) = @_; my $properties = $class->additional_parameters(); + my $rule_env = $class->rule_env(); + $class->register_method({ name => 'get_rules', path => '', method => 'GET', description => "List rules.", + permissions => &$rules_audit_permissions($rule_env), parameters => { additionalProperties => 0, properties => $properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => 'array', items => { @@ -79,18 +126,16 @@ sub register_get_rules { code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); - - my $digest = $fw_conf->{digest}; + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); - my $res = []; + my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules); my $ind = 0; - foreach my $rule (@$rules) { - push @$res, PVE::Firewall::cleanup_fw_rule($rule, $digest, $ind++); + foreach my $rule (@$list) { + $rule->{pos} = $ind++; } - return $res; + return $list; }}); } @@ -101,15 +146,19 @@ sub register_get_rule { $properties->{pos} = $api_properties->{pos}; + my $rule_env = $class->rule_env(); + $class->register_method({ name => 'get_rule', path => '{pos}', method => 'GET', description => "Get single rule data.", + permissions => &$rules_audit_permissions($rule_env), parameters => { additionalProperties => 0, properties => $properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => "object", properties => { @@ -121,16 +170,16 @@ sub register_get_rule { code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); - my $digest = $fw_conf->{digest}; - # fixme: check digest + my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules); - die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); + die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list); - my $rule = $rules->[$param->{pos}]; - - return PVE::Firewall::cleanup_fw_rule($rule, $digest, $param->{pos}); + my $rule = $list->[$param->{pos}]; + $rule->{pos} = $param->{pos}; + + return $rule; }}); } @@ -143,28 +192,30 @@ sub register_create_rule { $create_rule_properties->{action}->{optional} = 0; $create_rule_properties->{type}->{optional} = 0; + my $rule_env = $class->rule_env(); + $class->register_method({ name => 'create_rule', path => '', method => 'POST', description => "Create new rule.", protected => 1, + permissions => &$rules_modify_permissions($rule_env), parameters => { additionalProperties => 0, properties => $create_rule_properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => "null" }, code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); - - my $digest = $fw_conf->{digest}; + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); my $rule = {}; PVE::Firewall::copy_rule_data($rule, $param); - PVE::Firewall::verify_rule($rule, $class->allow_groups()); + PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env()); $rule->{enable} = 0 if !defined($param->{enable}); @@ -183,6 +234,8 @@ sub register_update_rule { $properties->{pos} = $api_properties->{pos}; + my $rule_env = $class->rule_env(); + $properties->{moveto} = { description => "Move rule to new position . Other arguments are ignored.", type => 'integer', @@ -204,19 +257,21 @@ sub register_update_rule { method => 'PUT', description => "Modify rule data.", protected => 1, + permissions => &$rules_modify_permissions($rule_env), parameters => { additionalProperties => 0, properties => $update_rule_properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => "null" }, code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); + + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules); + PVE::Tools::assert_if_modified($digest, $param->{digest}); - my $digest = $fw_conf->{digest}; - # fixme: check digest - die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); my $rule = $rules->[$param->{pos}]; @@ -234,16 +289,11 @@ sub register_update_rule { push @$newrules, $rule if $moveto >= scalar(@$rules); $rules = $newrules; } else { - raise_param_exc({ type => "property is missing"}) - if !defined($param->{type}); - raise_param_exc({ action => "property is missing"}) - if !defined($param->{action}); - PVE::Firewall::copy_rule_data($rule, $param); PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'}; - PVE::Firewall::verify_rule($rule, $class->allow_groups()); + PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env()); } $class->save_rules($param, $fw_conf, $rules); @@ -258,25 +308,31 @@ sub register_delete_rule { my $properties = $class->additional_parameters(); $properties->{pos} = $api_properties->{pos}; + + $properties->{digest} = get_standard_option('pve-config-digest'); + my $rule_env = $class->rule_env(); + $class->register_method({ name => 'delete_rule', path => '{pos}', method => 'DELETE', description => "Delete rule.", protected => 1, + permissions => &$rules_modify_permissions($rule_env), parameters => { additionalProperties => 0, properties => $properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => "null" }, code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); - my $digest = $fw_conf->{digest}; - # fixme: check digest + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules); + PVE::Tools::assert_if_modified($digest, $param->{digest}); die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); @@ -308,8 +364,11 @@ 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 { @@ -319,16 +378,47 @@ sub load_config { my $rules = $fw_conf->{groups}->{$param->{group}}; die "no such security group '$param->{group}'\n" if !defined($rules); - return ($fw_conf, $rules); + return (undef, $fw_conf, $rules); } sub save_rules { my ($class, $param, $fw_conf, $rules) = @_; - $fw_conf->{groups}->{$param->{group}} = $rules; + if (!defined($rules)) { + delete $fw_conf->{groups}->{$param->{group}}; + } else { + $fw_conf->{groups}->{$param->{group}} = $rules; + } + PVE::Firewall::save_clusterfw_conf($fw_conf); } +__PACKAGE__->register_method({ + name => 'delete_security_group', + path => '', + method => 'DELETE', + description => "Delete security group.", + protected => 1, + parameters => { + additionalProperties => 0, + properties => { + group => get_standard_option('pve-security-group-name'), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my (undef, $cluster_conf, $rules) = __PACKAGE__->load_config($param); + + die "Security group '$param->{group}' is not empty\n" + if scalar(@$rules); + + __PACKAGE__->save_rules($param, $cluster_conf, undef); + + return undef; + }}); + __PACKAGE__->register_handlers(); package PVE::API2::Firewall::ClusterRules; @@ -338,13 +428,19 @@ use warnings; use base qw(PVE::API2::Firewall::RulesBase); +sub rule_env { + my ($class, $param) = @_; + + return 'cluster'; +} + sub load_config { my ($class, $param) = @_; my $fw_conf = PVE::Firewall::load_clusterfw_conf(); my $rules = $fw_conf->{rules}; - return ($fw_conf, $rules); + return (undef, $fw_conf, $rules); } sub save_rules { @@ -366,13 +462,20 @@ 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) = @_; - my $fw_conf = PVE::Firewall::load_hostfw_conf(); + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); + my $fw_conf = PVE::Firewall::load_hostfw_conf($cluster_conf); my $rules = $fw_conf->{rules}; - return ($fw_conf, $rules); + return ($cluster_conf, $fw_conf, $rules); } sub save_rules { @@ -397,13 +500,58 @@ __PACKAGE__->additional_parameters({ vmid => get_standard_option('pve-vmid'), }); +sub rule_env { + my ($class, $param) = @_; + + return 'vm'; +} + +sub load_config { + my ($class, $param) = @_; + + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); + my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid}); + my $rules = $fw_conf->{rules}; + + return ($cluster_conf, $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 $cluster_conf = PVE::Firewall::load_clusterfw_conf(); + my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid}); my $rules = $fw_conf->{rules}; - return ($fw_conf, $rules); + return ($cluster_conf, $fw_conf, $rules); } sub save_rules {