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 44A7867834 for ; Fri, 31 Jul 2020 19:17:10 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1D61A1EF0E for ; Fri, 31 Jul 2020 19:17:09 +0200 (CEST) Received: from mailpro.odiso.net (mailpro.odiso.net [89.248.211.110]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 2107A1ED80 for ; Fri, 31 Jul 2020 19:16:55 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by mailpro.odiso.net (Postfix) with ESMTP id 411F1122E4E2; Fri, 31 Jul 2020 19:16:49 +0200 (CEST) Received: from mailpro.odiso.net ([127.0.0.1]) by localhost (mailpro.odiso.net [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id f0Sme_fwNpKT; Fri, 31 Jul 2020 19:16:49 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by mailpro.odiso.net (Postfix) with ESMTP id 26921122E4E6; Fri, 31 Jul 2020 19:16:49 +0200 (CEST) X-Virus-Scanned: amavisd-new at mailpro.odiso.com Received: from mailpro.odiso.net ([127.0.0.1]) by localhost (mailpro.odiso.net [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id tkhexlc7XMSO; Fri, 31 Jul 2020 19:16:49 +0200 (CEST) Received: from pve.fritz.box (unknown [213.211.148.86]) by mailpro.odiso.net (Postfix) with ESMTPSA id E58E9122E4E2; Fri, 31 Jul 2020 19:16:48 +0200 (CEST) From: Alexandre Derumier To: pve-devel@pve.proxmox.com Date: Fri, 31 Jul 2020 19:16:30 +0200 Message-Id: <20200731171643.13180-2-aderumier@odiso.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200731171643.13180-1-aderumier@odiso.com> References: <20200731171643.13180-1-aderumier@odiso.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SPAM-LEVEL: Spam detection results: 0 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [subnetplugin.pm, vnetplugin.pm, sdn.pm, zones.pm, vnets.pm, subnets.pm, controllers.pm] Subject: [pve-devel] [PATCH v5 pve-network 01/14] 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: Fri, 31 Jul 2020 17:17:10 -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; =20 use base qw(PVE::RESTHandler); =20 @@ -32,6 +33,11 @@ __PACKAGE__->register_method ({ path =3D> 'controllers', }); =20 +__PACKAGE__->register_method ({ + subclass =3D> "PVE::API2::Network::SDN::Subnets", + path =3D> 'subnets', +}); + __PACKAGE__->register_method({ name =3D> 'index', path =3D> '', @@ -61,6 +67,7 @@ __PACKAGE__->register_method({ { id =3D> 'vnets' }, { id =3D> 'zones' }, { id =3D> 'controllers' }, + { id =3D> 'subnets' }, ]; =20 return $res; diff --git a/PVE/API2/Network/SDN/Makefile b/PVE/API2/Network/SDN/Makefil= e index 6f20d4a..59626fa 100644 --- a/PVE/API2/Network/SDN/Makefile +++ b/PVE/API2/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=3DVnets.pm Zones.pm Controllers.pm +SOURCES=3DVnets.pm Zones.pm Controllers.pm Subnets.pm =20 =20 PERL5DIR=3D${DESTDIR}/usr/share/perl5 diff --git a/PVE/API2/Network/SDN/Subnets.pm b/PVE/API2/Network/SDN/Subne= ts.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 =3D sub { + my ($cfg, $id) =3D @_; + + my $scfg =3D dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($= cfg, $id)); + $scfg->{subnet} =3D $id; + $scfg->{digest} =3D $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name =3D> 'index', + path =3D> '', + method =3D> 'GET', + description =3D> "SDN subnets index.", + permissions =3D> { + description =3D> "Only list entries where you have 'SDN.Audit' or 'SDN.= Allocate' permissions on '/sdn/subnets/'", + user =3D> 'all', + }, + parameters =3D> { + additionalProperties =3D> 0, + }, + returns =3D> { + type =3D> 'array', + items =3D> { + type =3D> "object", + properties =3D> {}, + }, + links =3D> [ { rel =3D> 'child', href =3D> "{subnet}" } ], + }, + code =3D> sub { + my ($param) =3D @_; + + my $rpcenv =3D PVE::RPCEnvironment::get(); + my $authuser =3D $rpcenv->get_user(); + + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + + my @sids =3D PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); + my $res =3D []; + foreach my $id (@sids) { + my $privs =3D [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/subnets/$id", $privs, = 1); + + my $scfg =3D &$api_sdn_subnets_config($cfg, $id); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name =3D> 'read', + path =3D> '{subnet}', + method =3D> 'GET', + description =3D> "Read sdn subnet configuration.", + permissions =3D> { + check =3D> ['perm', '/sdn/subnets/{subnet}', ['SDN.Allocate']], + }, + + parameters =3D> { + additionalProperties =3D> 0, + properties =3D> { + subnet =3D> get_standard_option('pve-sdn-subnet-id', { + completion =3D> \&PVE::Network::SDN::Subnets::complete_s= dn_subnets, + }), + }, + }, + returns =3D> { type =3D> 'object' }, + code =3D> sub { + my ($param) =3D @_; + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + + return &$api_sdn_subnets_config($cfg, $param->{subnet}); + }}); + +__PACKAGE__->register_method ({ + name =3D> 'create', + protected =3D> 1, + path =3D> '', + method =3D> 'POST', + description =3D> "Create a new sdn subnet object.", + permissions =3D> { + check =3D> ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters =3D> PVE::Network::SDN::SubnetPlugin->createSchema(), + returns =3D> { type =3D> 'null' }, + code =3D> sub { + my ($param) =3D @_; + + my $type =3D extract_param($param, 'type'); + my $id =3D 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 =3D PVE::Network::SDN::Subnets::config(); + my $opts =3D PVE::Network::SDN::SubnetPlugin->check_config($id, $param= , 1, 1); + + my $scfg =3D undef; + if ($scfg =3D PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id= , 1)) { + die "sdn subnet object ID '$id' already defined\n"; + } + + $cfg->{ids}->{$id} =3D $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 =3D> 'update', + protected =3D> 1, + path =3D> '{subnet}', + method =3D> 'PUT', + description =3D> "Update sdn subnet object configuration.", + permissions =3D> { + check =3D> ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters =3D> PVE::Network::SDN::SubnetPlugin->updateSchema(), + returns =3D> { type =3D> 'null' }, + code =3D> sub { + my ($param) =3D @_; + + my $id =3D extract_param($param, 'subnet'); + my $digest =3D extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + my $opts =3D PVE::Network::SDN::SubnetPlugin->check_config($id, $pa= ram, 0, 1); + $cfg->{ids}->{$id} =3D $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 =3D> 'delete', + protected =3D> 1, + path =3D> '{subnet}', + method =3D> 'DELETE', + description =3D> "Delete sdn subnet object configuration.", + permissions =3D> { + check =3D> ['perm', '/sdn/subnets', ['SDN.Allocate']], + }, + parameters =3D> { + additionalProperties =3D> 0, + properties =3D> { + subnet =3D> get_standard_option('pve-sdn-subnet-id', { + completion =3D> \&PVE::Network::SDN::Subnets::complete_s= dn_subnets, + }), + }, + }, + returns =3D> { type =3D> 'null' }, + code =3D> sub { + my ($param) =3D @_; + + my $id =3D extract_param($param, 'subnet'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + + my $scfg =3D PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id)= ; + + my $subnet_cfg =3D 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=3DVnets.pm VnetPlugin.pm Zones.pm Controllers.pm +SOURCES=3DVnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm Subn= etPlugin.pm =20 =20 PERL5DIR=3D${DESTDIR}/usr/share/perl5 diff --git a/PVE/Network/SDN/SubnetPlugin.pm b/PVE/Network/SDN/SubnetPlug= in.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 =3D> "The SDN subnet object identifier.", + type =3D> 'string', format =3D> 'pve-sdn-subnet-id', + type =3D> 'string' +}); + +PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet= _id); +sub parse_sdn_subnet_id { + my ($id, $noerr) =3D @_; + + my $cidr =3D $id =3D~ 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 =3D { + + propertyList =3D> { + subnet =3D> get_standard_option('pve-sdn-subnet-id', + { completion =3D> \&PVE::Network::SDN::Subnets::complete_sdn= _subnet }), + }, +}; + +sub type { + return 'subnet'; +} + +sub private { + return $defaultData; +} + +sub properties { + return { + gateway =3D> { + type =3D> 'string', format =3D> 'ip', + description =3D> "Subnet Gateway: Will be assign on vnet for= layer3 zones", + }, + snat =3D> { + type =3D> 'boolean', + description =3D> "enable masquerade for this subnet if pve-f= irewall", + }, + #cloudinit, dhcp options + routes =3D> { + type =3D> 'string', + description =3D> "static routes [network=3D:gateway= =3D,network=3D:gateway=3D,... ]", + }, + #cloudinit, dhcp options + nameservers =3D> { + type =3D> 'string', format =3D> 'address-list', + description =3D> " dns nameserver", + }, + #cloudinit, dhcp options + searchdomain =3D> { + type =3D> 'string', + }, + dhcp =3D> { + type =3D> 'boolean', + description =3D> "enable dhcp for this subnet", + }, + dns_driver =3D> { + type =3D> 'string', + description =3D> "Develop some dns registrations plugins (po= werdns,...)", + }, + ipam_driver =3D> { + type =3D> 'string', + description =3D> "use a specific ipam", + }, + }; +} + +sub options { + return { + gateway =3D> { optional =3D> 1 }, + routes =3D> { optional =3D> 1 }, + nameservers =3D> { optional =3D> 1 }, + searchdomain =3D> { optional =3D> 1 }, + snat =3D> { optional =3D> 1 }, + dhcp =3D> { optional =3D> 1 }, + dns_driver =3D> { optional =3D> 1 }, + ipam_driver =3D> { optional =3D> 1 }, + }; +} + +sub on_update_hook { + my ($class, $subnetid, $subnet_cfg) =3D @_; + + my $subnet =3D $subnetid =3D~ s/-/\//r; + my $subnet_matcher =3D subnet_matcher($subnet); + + my $gateway =3D $subnet_cfg->{ids}->{$subnetid}->{gateway}; + raise_param_exc({ gateway =3D> "$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) =3D @_; + + die "no sdn subnet ID specified\n" if !$id; + + my $scfg =3D $cfg->{ids}->{$id}; + die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); + + return $scfg; +} + +sub config { + my $config =3D cfs_read_file("sdn/subnets.cfg"); +} + +sub write_config { + my ($cfg) =3D @_; + + cfs_write_file("sdn/subnets.cfg", $cfg); +} + +sub sdn_subnets_ids { + my ($cfg) =3D @_; + + return keys %{$cfg->{ids}}; +} + +sub complete_sdn_subnet { + my ($cmdname, $pname, $cvalue) =3D @_; + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_s= ubnets_ids($cfg) ]; +} + +sub get_subnet { + my ($subnetid) =3D @_; + + my $cfg =3D PVE::Network::SDN::Subnets::config(); + my $subnet =3D 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 (>=3D 5.0-45), perl (>=3D 5.6.0-16), pve-cluster (>=3D 5.0-32), + libnet-subnet-perl, ${misc:Depends}, ${perl:Depends}, Recommends: frr-pythontools, ifupdown2 --=20 2.20.1