From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D6EFE619FC for ; Mon, 28 Sep 2020 10:44:19 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 759782B54B for ; Mon, 28 Sep 2020 10:43:49 +0200 (CEST) Received: from kvmformation1.odiso.net (globalOdiso.M6Lille.odiso.net [89.248.211.242]) by firstgate.proxmox.com (Proxmox) with ESMTP id C47BE2B401 for ; Mon, 28 Sep 2020 10:43:37 +0200 (CEST) Received: by kvmformation1.odiso.net (Postfix, from userid 0) id 97163E2FEE; Mon, 28 Sep 2020 10:43:37 +0200 (CEST) From: Alexandre Derumier To: pve-devel@lists.proxmox.com Date: Mon, 28 Sep 2020 10:43:11 +0200 Message-Id: <20200928084336.3487196-2-aderumier@odiso.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200928084336.3487196-1-aderumier@odiso.com> References: <20200928084336.3487196-1-aderumier@odiso.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -0.477 Adjusted score from AWL reputation of From: address HEADER_FROM_DIFFERENT_DOMAINS 0.078 From and EnvelopeFrom 2nd level mail domains are different 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 KHOP_HELO_FCRDNS 0.399 Relay HELO differs from its IP's reverse DNS NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records 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 v9 pve-network 01/26] add subnet plugin 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: , X-List-Received-Date: Mon, 28 Sep 2020 08:44:19 -0000 Signed-off-by: Alexandre Derumier --- PVE/API2/Network/SDN.pm | 7 + PVE/API2/Network/SDN/Makefile | 2 +- PVE/API2/Network/SDN/Subnets.pm | 219 ++++++++++++++++++++++++++++++++ PVE/Network/SDN/Makefile | 2 +- PVE/Network/SDN/SubnetPlugin.pm | 115 +++++++++++++++++ PVE/Network/SDN/Subnets.pm | 55 ++++++++ debian/control | 1 + 7 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 PVE/API2/Network/SDN/Subnets.pm create mode 100644 PVE/Network/SDN/SubnetPlugin.pm create mode 100644 PVE/Network/SDN/Subnets.pm diff --git a/PVE/API2/Network/SDN.pm b/PVE/API2/Network/SDN.pm index 3f497fc..38af746 100644 --- a/PVE/API2/Network/SDN.pm +++ b/PVE/API2/Network/SDN.pm @@ -14,6 +14,7 @@ use PVE::Tools qw(run_command); use PVE::API2::Network::SDN::Controllers; use PVE::API2::Network::SDN::Vnets; use PVE::API2::Network::SDN::Zones; +use PVE::API2::Network::SDN::Subnets; use base qw(PVE::RESTHandler); @@ -32,6 +33,11 @@ __PACKAGE__->register_method ({ path => 'controllers', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Subnets", + path => 'subnets', +}); + __PACKAGE__->register_method({ name => 'index', path => '', @@ -61,6 +67,7 @@ __PACKAGE__->register_method({ { id => 'vnets' }, { id => 'zones' }, { id => 'controllers' }, + { id => 'subnets' }, ]; return $res; diff --git a/PVE/API2/Network/SDN/Makefile b/PVE/API2/Network/SDN/Makefile index 6f20d4a..59626fa 100644 --- a/PVE/API2/Network/SDN/Makefile +++ b/PVE/API2/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=Vnets.pm Zones.pm Controllers.pm +SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm PERL5DIR=${DESTDIR}/usr/share/perl5 diff --git a/PVE/API2/Network/SDN/Subnets.pm b/PVE/API2/Network/SDN/Subnets.pm new file mode 100644 index 0000000..26b2aa5 --- /dev/null +++ b/PVE/API2/Network/SDN/Subnets.pm @@ -0,0 +1,219 @@ +package PVE::API2::Network::SDN::Subnets; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::SubnetPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $api_sdn_subnets_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id)); + $scfg->{subnet} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN subnets index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{subnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + + my $cfg = PVE::Network::SDN::Subnets::config(); + + my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/subnets/$id", $privs, 1); + + my $scfg = &$api_sdn_subnets_config($cfg, $id); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{subnet}', + method => 'GET', + description => "Read sdn subnet configuration.", + permissions => { + check => ['perm', '/sdn/subnets/{subnet}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = PVE::Network::SDN::Subnets::config(); + + return &$api_sdn_subnets_config($cfg, $param->{subnet}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn subnet object.", + permissions => { + check => ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'subnet'); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { + die "sdn subnet object ID '$id' already defined\n"; + } + + $cfg->{ids}->{$id} = $opts; + PVE::Network::SDN::SubnetPlugin->on_update_hook($id, $cfg); + PVE::Network::SDN::Subnets::write_config($cfg); + PVE::Network::SDN::increase_version(); + + }, "create sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{subnet}', + method => 'PUT', + description => "Update sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); + $cfg->{ids}->{$id} = $opts; + + PVE::Network::SDN::SubnetPlugin->on_update_hook($id, $cfg); + PVE::Network::SDN::Subnets::write_config($cfg); + PVE::Network::SDN::increase_version(); + + }, "update sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{subnet}', + method => 'DELETE', + description => "Delete sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + + my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + + my $subnet_cfg = PVE::Network::SDN::Subnets::config(); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Subnets::write_config($cfg); + PVE::Network::SDN::increase_version(); + + }, "delete sdn subnet object failed"); + + + return undef; + }}); + +1; diff --git a/PVE/Network/SDN/Makefile b/PVE/Network/SDN/Makefile index 7622255..59f8c34 100644 --- a/PVE/Network/SDN/Makefile +++ b/PVE/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm +SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm PERL5DIR=${DESTDIR}/usr/share/perl5 diff --git a/PVE/Network/SDN/SubnetPlugin.pm b/PVE/Network/SDN/SubnetPlugin.pm new file mode 100644 index 0000000..8900681 --- /dev/null +++ b/PVE/Network/SDN/SubnetPlugin.pm @@ -0,0 +1,115 @@ +package PVE::Network::SDN::SubnetPlugin; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use base qw(PVE::SectionConfig); +use PVE::JSONSchema qw(get_standard_option); +use PVE::Exception qw(raise raise_param_exc); +use Net::Subnet qw(subnet_matcher); + +PVE::Cluster::cfs_register_file('sdn/subnets.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }); + +PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', { + description => "The SDN subnet object identifier.", + type => 'string', format => 'pve-sdn-subnet-id', + type => 'string' +}); + +PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id); +sub parse_sdn_subnet_id { + my ($id, $noerr) = @_; + + my $cidr = $id =~ s/-/\//r; + + if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) || + PVE::JSONSchema::pve_verify_cidrv6($cidr, 1))) + { + return undef if $noerr; + die "value does not look like a valid CIDR network\n"; + } + return $id; +} + +my $defaultData = { + + propertyList => { + subnet => get_standard_option('pve-sdn-subnet-id', + { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }), + }, +}; + +sub type { + return 'subnet'; +} + +sub private { + return $defaultData; +} + +sub properties { + return { + gateway => { + type => 'string', format => 'ip', + description => "Subnet Gateway: Will be assign on vnet for layer3 zones", + }, + snat => { + type => 'boolean', + description => "enable masquerade for this subnet if pve-firewall", + }, + #cloudinit, dhcp options + routes => { + type => 'string', + description => "static routes [network=:gateway=,network=:gateway=,... ]", + }, + #cloudinit, dhcp options + nameservers => { + type => 'string', format => 'address-list', + description => " dns nameserver", + }, + #cloudinit, dhcp options + searchdomain => { + type => 'string', + }, + dhcp => { + type => 'boolean', + description => "enable dhcp for this subnet", + }, + dns_driver => { + type => 'string', + description => "Develop some dns registrations plugins (powerdns,...)", + }, + ipam_driver => { + type => 'string', + description => "use a specific ipam", + }, + }; +} + +sub options { + return { + gateway => { optional => 1 }, + routes => { optional => 1 }, + nameservers => { optional => 1 }, + searchdomain => { optional => 1 }, + snat => { optional => 1 }, + dhcp => { optional => 1 }, + dns_driver => { optional => 1 }, + ipam_driver => { optional => 1 }, + }; +} + +sub on_update_hook { + my ($class, $subnetid, $subnet_cfg) = @_; + + my $subnet = $subnetid =~ s/-/\//r; + my $subnet_matcher = subnet_matcher($subnet); + + my $gateway = $subnet_cfg->{ids}->{$subnetid}->{gateway}; + raise_param_exc({ gateway => "$gateway is not in subnet $subnet"}) if $gateway && !$subnet_matcher->($gateway); +} + +1; diff --git a/PVE/Network/SDN/Subnets.pm b/PVE/Network/SDN/Subnets.pm new file mode 100644 index 0000000..454a9cf --- /dev/null +++ b/PVE/Network/SDN/Subnets.pm @@ -0,0 +1,55 @@ +package PVE::Network::SDN::Subnets; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); + +use PVE::Network::SDN::SubnetPlugin; +PVE::Network::SDN::SubnetPlugin->register(); +PVE::Network::SDN::SubnetPlugin->init(); + +sub sdn_subnets_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn subnet ID specified\n" if !$id; + + my $scfg = $cfg->{ids}->{$id}; + die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); + + return $scfg; +} + +sub config { + my $config = cfs_read_file("sdn/subnets.cfg"); +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/subnets.cfg", $cfg); +} + +sub sdn_subnets_ids { + my ($cfg) = @_; + + return keys %{$cfg->{ids}}; +} + +sub complete_sdn_subnet { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::Subnets::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ]; +} + +sub get_subnet { + my ($subnetid) = @_; + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); + return $subnet; +} + +1; diff --git a/debian/control b/debian/control index afdf573..8b67d74 100644 --- a/debian/control +++ b/debian/control @@ -16,6 +16,7 @@ Breaks: pve-manager (<< 5.2-12) Depends: libpve-common-perl (>= 5.0-45), perl (>= 5.6.0-16), pve-cluster (>= 5.0-32), + libnet-subnet-perl, ${misc:Depends}, ${perl:Depends}, Recommends: frr-pythontools, ifupdown2 -- 2.20.1