From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id C3BAB1FF163 for ; Thu, 10 Oct 2024 18:03:21 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5E9301EE19; Thu, 10 Oct 2024 18:03:21 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 10 Oct 2024 17:56:44 +0200 Message-Id: <20241010155650.255698-12-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20241010155650.255698-1-s.hanreich@proxmox.com> References: <20241010155650.255698-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.251 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH pve-firewall v2 11/17] api: add vnet endpoints X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Signed-off-by: Stefan Hanreich --- src/PVE/API2/Firewall/Makefile | 1 + src/PVE/API2/Firewall/Rules.pm | 84 +++++++++++++++++ src/PVE/API2/Firewall/Vnet.pm | 168 +++++++++++++++++++++++++++++++++ src/PVE/Firewall.pm | 10 ++ 4 files changed, 263 insertions(+) create mode 100644 src/PVE/API2/Firewall/Vnet.pm diff --git a/src/PVE/API2/Firewall/Makefile b/src/PVE/API2/Firewall/Makefile index e916755..325c4d3 100644 --- a/src/PVE/API2/Firewall/Makefile +++ b/src/PVE/API2/Firewall/Makefile @@ -9,6 +9,7 @@ LIB_SOURCES= \ Cluster.pm \ Host.pm \ VM.pm \ + Vnet.pm \ Groups.pm all: diff --git a/src/PVE/API2/Firewall/Rules.pm b/src/PVE/API2/Firewall/Rules.pm index f5cb002..e57b3de 100644 --- a/src/PVE/API2/Firewall/Rules.pm +++ b/src/PVE/API2/Firewall/Rules.pm @@ -18,6 +18,25 @@ my $api_properties = { }, }; +=head3 check_privileges_for_method($class, $method_name, $param) + +If the permission checks from the register_method() call are not sufficient, +this function can be overriden for performing additional permission checks +before API methods are executed. If the permission check fails, this function +should die with an appropriate error message. The name of the method calling +this function is provided by C<$method_name> and the parameters of the API +method are provided by C<$param> + +Default implementation is a no-op to preserve backwards compatibility with +existing subclasses, since this got added later on. It preserves existing +behavior without having to change every subclass. + +=cut + +sub check_privileges_for_method { + my ($class, $method_name, $param) = @_; +} + sub lock_config { my ($class, $param, $code) = @_; @@ -94,6 +113,8 @@ sub register_get_rules { code => sub { my ($param) = @_; + $class->check_privileges_for_method('get_rules', $param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules); @@ -192,6 +213,8 @@ sub register_get_rule { code => sub { my ($param) = @_; + $class->check_privileges_for_method('get_rule', $param); + my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param); my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules); @@ -232,6 +255,8 @@ sub register_create_rule { code => sub { my ($param) = @_; + $class->check_privileges_for_method('create_rule', $param); + $class->lock_config($param, sub { my ($param) = @_; @@ -293,6 +318,8 @@ sub register_update_rule { code => sub { my ($param) = @_; + $class->check_privileges_for_method('update_rule', $param); + $class->lock_config($param, sub { my ($param) = @_; @@ -359,6 +386,8 @@ sub register_delete_rule { code => sub { my ($param) = @_; + $class->check_privileges_for_method('delete_rule', $param); + $class->lock_config($param, sub { my ($param) = @_; @@ -634,4 +663,59 @@ sub save_rules { __PACKAGE__->register_handlers(); +package PVE::API2::Firewall::VnetRules; + +use strict; +use warnings; +use PVE::JSONSchema qw(get_standard_option); + +use base qw(PVE::API2::Firewall::RulesBase); + +__PACKAGE__->additional_parameters({ + vnet => get_standard_option('pve-sdn-vnet-id'), +}); + +sub check_privileges_for_method { + my ($class, $method_name, $param) = @_; + + if ($method_name eq 'get_rule' || $method_name eq 'get_rules') { + PVE::API2::Firewall::Vnet::check_vnet_access($param->{vnet}, ['SDN.Audit', 'SDN.Allocate']); + } elsif ($method_name =~ '(update|create|delete)_rule') { + PVE::API2::Firewall::Vnet::check_vnet_access($param->{vnet}, ['SDN.Allocate']); + } else { + die "unknown method: $method_name"; + } +} + +sub rule_env { + my ($class, $param) = @_; + + return 'vnet'; +} + +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vnetfw_conf($param->{vnet}, 10, $code, $param); +} + +sub load_config { + my ($class, $param) = @_; + + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, { load_sdn_config => 1 }); + my $fw_conf = PVE::Firewall::load_vnetfw_conf($cluster_conf, 'vnet', $param->{vnet}); + 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_vnetfw_conf($param->{vnet}, $fw_conf); +} + +__PACKAGE__->register_handlers(); + 1; diff --git a/src/PVE/API2/Firewall/Vnet.pm b/src/PVE/API2/Firewall/Vnet.pm new file mode 100644 index 0000000..cb49b67 --- /dev/null +++ b/src/PVE/API2/Firewall/Vnet.pm @@ -0,0 +1,168 @@ +package PVE::API2::Firewall::Vnet; + +use strict; +use warnings; + +use Storable qw(dclone); + +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::Firewall; +use PVE::API2::Firewall::Rules; + + +use base qw(PVE::RESTHandler); + +sub check_vnet_access { + my ($vnetid, $privileges) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid, 1) + or die "invalid vnet specified"; + + my $zoneid = $vnet->{zone}; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + $rpcenv->check_any($authuser, "/sdn/zones/$zoneid/$vnetid", $privileges); +}; + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Firewall::VnetRules", + path => 'rules', +}); + +__PACKAGE__->register_method({ + name => 'index', + path => '', + method => 'GET', + description => "Directory index.", + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + }, + }, + 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 = dclone($PVE::Firewall::vnet_option_properties); + +my sub add_option_properties { + my ($properties) = @_; + + foreach my $k (keys %$option_properties) { + $properties->{$k} = $option_properties->{$k}; + } + + return $properties; +}; + + +__PACKAGE__->register_method({ + name => 'get_options', + path => 'options', + method => 'GET', + description => "Get vnet firewall options.", + permissions => { + description => "Needs SDN.Audit or SDN.Allocate permissions on '/sdn/zones//'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + }, + }, + returns => { + type => "object", + properties => $option_properties, + }, + code => sub { + my ($param) = @_; + + check_vnet_access($param->{vnet}, ['SDN.Allocate', 'SDN.Audit']); + + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); + my $vnetfw_conf = PVE::Firewall::load_vnetfw_conf($cluster_conf, 'vnet', $param->{vnet}); + + return PVE::Firewall::copy_opject_with_digest($vnetfw_conf->{options}); + }}); + +__PACKAGE__->register_method({ + name => 'set_options', + path => 'options', + method => 'PUT', + description => "Set Firewall options.", + protected => 1, + permissions => { + description => "Needs SDN.Allocate permissions on '/sdn/zones//'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => add_option_properties({ + vnet => get_standard_option('pve-sdn-vnet-id'), + delete => { + type => 'string', format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }, + digest => get_standard_option('pve-config-digest'), + }), + }, + returns => { type => "null" }, + code => sub { + my ($param) = @_; + + check_vnet_access($param->{vnet}, ['SDN.Allocate']); + + PVE::Firewall::lock_vnetfw_conf($param->{vnet}, 10, sub { + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); + my $vnetfw_conf = PVE::Firewall::load_vnetfw_conf($cluster_conf, 'vnet', $param->{vnet}); + + my (undef, $digest) = PVE::Firewall::copy_opject_with_digest($vnetfw_conf->{options}); + PVE::Tools::assert_if_modified($digest, $param->{digest}); + + if ($param->{delete}) { + for my $opt (PVE::Tools::split_list($param->{delete})) { + raise_param_exc({ delete => "no such option '$opt'" }) + if !$option_properties->{$opt}; + delete $vnetfw_conf->{options}->{$opt}; + } + } + + if (defined($param->{enable})) { + $param->{enable} = $param->{enable} ? 1 : 0; + } + + for my $k (keys %$option_properties) { + next if !defined($param->{$k}); + $vnetfw_conf->{options}->{$k} = $param->{$k}; + } + + PVE::Firewall::save_vnetfw_conf($param->{vnet}, $vnetfw_conf); + }); + + return undef; + }}); + +1; diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index e8096aa..1a78caf 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -1912,6 +1912,11 @@ sub rules_modify_permissions { return { check => ['perm', '/vms/{vmid}', [ 'VM.Config.Network' ]], } + } elsif ($rule_env eq 'vnet') { + return { + description => "Needs SDN.Allocate permissions on '/sdn/zones//'", + user => 'all', + } } return undef; @@ -1932,6 +1937,11 @@ sub rules_audit_permissions { return { check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], } + } elsif ($rule_env eq 'vnet') { + return { + description => "Needs SDN.Audit or SDN.Allocate permissions on '/sdn/zones//'", + user => 'all', + } } return undef; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel