X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FFirewall%2FRules.pm;h=44756637117b1e7b2ad762d92c57a0dd8833843b;hp=16e22badce94110d9d7e4fcfed4f8c0b569559e9;hb=HEAD;hpb=63c91681ed5073e1e659860a1948ed852e061fee diff --git a/src/PVE/API2/Firewall/Rules.pm b/src/PVE/API2/Firewall/Rules.pm index 16e22ba..9fcfb20 100644 --- a/src/PVE/API2/Firewall/Rules.pm +++ b/src/PVE/API2/Firewall/Rules.pm @@ -2,13 +2,15 @@ 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; use base qw(PVE::RESTHandler); -my $api_properties = { +my $api_properties = { pos => { description => "Rule position.", type => 'integer', @@ -16,12 +18,18 @@ my $api_properties = { }, }; +sub lock_config { + my ($class, $param, $code) = @_; + + die "implement this in subclass"; +} + sub load_config { my ($class, $param) = @_; die "implement this in subclass"; - #return ($fw_conf, $rules); + #return ($cluster_conf, $fw_conf, $rules); } sub save_rules { @@ -32,6 +40,12 @@ sub save_rules { my $additional_param_hash = {}; +sub rule_env { + my ($class, $param) = @_; + + die "implement this in subclass"; +} + sub additional_parameters { my ($class, $new_value) = @_; @@ -51,15 +65,19 @@ sub register_get_rules { my $properties = $class->additional_parameters(); + my $rule_env = $class->rule_env(); + $class->register_method({ name => 'get_rules', path => '', method => 'GET', description => "List rules.", + permissions => PVE::Firewall::rules_audit_permissions($rule_env), parameters => { additionalProperties => 0, properties => $properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => 'array', items => { @@ -75,18 +93,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; }}); } @@ -96,37 +112,94 @@ sub register_get_rule { my $properties = $class->additional_parameters(); $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 => PVE::Firewall::rules_audit_permissions($rule_env), parameters => { additionalProperties => 0, properties => $properties, }, + proxyto => $rule_env eq 'host' ? 'node' : undef, returns => { type => "object", properties => { + action => { + type => 'string', + }, + comment => { + type => 'string', + optional => 1, + }, + dest => { + type => 'string', + optional => 1, + }, + dport => { + type => 'string', + optional => 1, + }, + enable => { + type => 'integer', + optional => 1, + }, + log => PVE::Firewall::get_standard_option('pve-fw-loglevel', { + description => 'Log level for firewall rule', + }), + 'icmp-type' => { + type => 'string', + optional => 1, + }, + iface => { + type => 'string', + optional => 1, + }, + ipversion => { + type => 'integer', + optional => 1, + }, + macro => { + type => 'string', + optional => 1, + }, pos => { type => 'integer', - } + }, + proto => { + type => 'string', + optional => 1, + }, + source => { + type => 'string', + optional => 1, + }, + sport => { + type => 'string', + optional => 1, + }, + type => { + type => 'string', + }, }, }, code => sub { my ($param) = @_; - my ($fw_conf, $rules) = $class->load_config($param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); + + my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules); + + die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list); - 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}]; - - return PVE::Firewall::cleanup_fw_rule($rule, $digest, $param->{pos}); + my $rule = $list->[$param->{pos}]; + $rule->{pos} = $param->{pos}; + + return $rule; }}); } @@ -136,6 +209,10 @@ sub register_create_rule { my $properties = $class->additional_parameters(); my $create_rule_properties = PVE::Firewall::add_rule_properties($properties); + $create_rule_properties->{action}->{optional} = 0; + $create_rule_properties->{type}->{optional} = 0; + + my $rule_env = $class->rule_env(); $class->register_method({ name => 'create_rule', @@ -143,25 +220,32 @@ sub register_create_rule { method => 'POST', description => "Create new rule.", protected => 1, + permissions => PVE::Firewall::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); + $class->lock_config($param, sub { + my ($param) = @_; - my $digest = $fw_conf->{digest}; - - my $rule = { type => 'out', action => 'ACCEPT', enable => 0}; + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); - PVE::Firewall::copy_rule_data($rule, $param); + my $rule = {}; - unshift @$rules, $rule; + PVE::Firewall::copy_rule_data($rule, $param); + PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env()); + + $rule->{enable} = 0 if !defined($param->{enable}); - $class->save_rules($param, $fw_conf, $rules); + unshift @$rules, $rule; + + $class->save_rules($param, $fw_conf, $rules); + }); return undef; }}); @@ -173,7 +257,9 @@ sub register_update_rule { my $properties = $class->additional_parameters(); $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', @@ -181,6 +267,12 @@ sub register_update_rule { optional => 1, }; + $properties->{delete} = { + type => 'string', format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }; + my $update_rule_properties = PVE::Firewall::add_rule_properties($properties); $class->register_method({ @@ -189,40 +281,50 @@ sub register_update_rule { method => 'PUT', description => "Modify rule data.", protected => 1, + permissions => PVE::Firewall::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 $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}]; - - my $moveto = $param->{moveto}; - if (defined($moveto) && $moveto != $param->{pos}) { - my $newrules = []; - for (my $i = 0; $i < scalar(@$rules); $i++) { - next if $i == $param->{pos}; - if ($i == $moveto) { - push @$newrules, $rule; + $class->lock_config($param, sub { + my ($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}); + + die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); + + my $rule = $rules->[$param->{pos}]; + + my $moveto = $param->{moveto}; + if (defined($moveto) && $moveto != $param->{pos}) { + my $newrules = []; + for (my $i = 0; $i < scalar(@$rules); $i++) { + next if $i == $param->{pos}; + if ($i == $moveto) { + push @$newrules, $rule; + } + push @$newrules, $rules->[$i]; } - push @$newrules, $rules->[$i]; + push @$newrules, $rule if $moveto >= scalar(@$rules); + $rules = $newrules; + } else { + PVE::Firewall::copy_rule_data($rule, $param); + + PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'}; + + PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env()); } - push @$newrules, $rule if $moveto >= scalar(@$rules); - $rules = $newrules; - } else { - PVE::Firewall::copy_rule_data($rule, $param); - } - $class->save_rules($param, $fw_conf, $rules); + $class->save_rules($param, $fw_conf, $rules); + }); return undef; }}); @@ -234,31 +336,41 @@ 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 => PVE::Firewall::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); + $class->lock_config($param, sub { + my ($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}); + + die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); - my $digest = $fw_conf->{digest}; - # fixme: check digest - - die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules); - - splice(@$rules, $param->{pos}, 1); - - $class->save_rules($param, $fw_conf, $rules); + splice(@$rules, $param->{pos}, 1); + + $class->save_rules($param, $fw_conf, $rules); + }); return undef; }}); @@ -278,14 +390,24 @@ package PVE::API2::Firewall::GroupRules; use strict; use warnings; +use PVE::JSONSchema qw(get_standard_option); use base qw(PVE::API2::Firewall::RulesBase); -__PACKAGE__->additional_parameters({ group => { - description => "Security group name.", - type => 'string', - maxLength => 20, # fixme: what length? -}}); +__PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') }); + + +sub rule_env { + my ($class, $param) = @_; + + return 'group'; +} + +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_clusterfw_conf(10, $code, $param); +} sub load_config { my ($class, $param) = @_; @@ -294,16 +416,54 @@ 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, + permissions => { + check => ['perm', '/', [ 'Sys.Modify' ]], + }, + parameters => { + additionalProperties => 0, + properties => { + group => get_standard_option('pve-security-group-name'), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + __PACKAGE__->lock_config($param, 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; @@ -313,13 +473,25 @@ use warnings; use base qw(PVE::API2::Firewall::RulesBase); +sub rule_env { + my ($class, $param) = @_; + + return 'cluster'; +} + +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_clusterfw_conf(10, $code, $param); +} + 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 { @@ -341,13 +513,26 @@ 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 lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_hostfw_conf(undef, 10, $code, $param); +} + 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 { @@ -359,4 +544,92 @@ sub save_rules { __PACKAGE__->register_handlers(); +package PVE::API2::Firewall::VMRules; + +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 'vm'; +} + +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + +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 lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + +sub load_config { + my ($class, $param) = @_; + + 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 ($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(); + 1;