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 DCDC066F66 for ; Sun, 8 Nov 2020 15:20:55 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5E2E6EC96 for ; Sun, 8 Nov 2020 15:20:15 +0100 (CET) Received: from kvmformation1.odiso.net (globalOdiso.M6Lille.odiso.net [89.248.211.242]) by firstgate.proxmox.com (Proxmox) with ESMTP id 59566E8BE for ; Sun, 8 Nov 2020 15:19:47 +0100 (CET) Received: by kvmformation1.odiso.net (Postfix, from userid 0) id 2A39171385C; Sun, 8 Nov 2020 15:19:42 +0100 (CET) From: Alexandre Derumier To: pve-devel@lists.proxmox.com Date: Sun, 8 Nov 2020 15:19:16 +0100 Message-Id: <20201108141940.1028443-15-aderumier@odiso.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201108141940.1028443-1-aderumier@odiso.com> References: <20201108141940.1028443-1-aderumier@odiso.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -0.202 Adjusted score from AWL reputation of From: address HEADER_FROM_DIFFERENT_DOMAINS 0.25 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.276 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mydomain.com, controllers.pm, ipams.pm, powerdnsplugin.pm, pveplugin.pm, sdn.pm, subnetplugin.pm, vnets.pm, plugin.pm, 192.in-addr.arpa, vnetplugin.pm, in-addr.arpa, zones.pm, dns.pm, subnets.pm] Subject: [pve-devel] [PATCH pve-network 14/38] add dns 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: Sun, 08 Nov 2020 14:20:55 -0000 --- PVE/API2/Network/SDN.pm | 7 + PVE/API2/Network/SDN/Dns.pm | 242 ++++++++++++++++++++++++++ PVE/API2/Network/SDN/Makefile | 2 +- PVE/Network/SDN/Dns.pm | 57 ++++++ PVE/Network/SDN/Dns/Makefile | 8 + PVE/Network/SDN/Dns/Plugin.pm | 117 +++++++++++++ PVE/Network/SDN/Dns/PowerdnsPlugin.pm | 201 +++++++++++++++++++++ PVE/Network/SDN/Ipams/PVEPlugin.pm | 1 - PVE/Network/SDN/Ipams/Plugin.pm | 2 +- PVE/Network/SDN/Makefile | 3 +- PVE/Network/SDN/SubnetPlugin.pm | 53 ++++-- PVE/Network/SDN/Subnets.pm | 156 +++++++++++++++-- PVE/Network/SDN/Vnets.pm | 12 +- 13 files changed, 814 insertions(+), 47 deletions(-) create mode 100644 PVE/API2/Network/SDN/Dns.pm create mode 100644 PVE/Network/SDN/Dns.pm create mode 100644 PVE/Network/SDN/Dns/Makefile create mode 100644 PVE/Network/SDN/Dns/Plugin.pm create mode 100644 PVE/Network/SDN/Dns/PowerdnsPlugin.pm diff --git a/PVE/API2/Network/SDN.pm b/PVE/API2/Network/SDN.pm index 6055fe5..0a5fa33 100644 --- a/PVE/API2/Network/SDN.pm +++ b/PVE/API2/Network/SDN.pm @@ -17,6 +17,7 @@ use PVE::API2::Network::SDN::Vnets; use PVE::API2::Network::SDN::Zones; use PVE::API2::Network::SDN::Subnets; use PVE::API2::Network::SDN::Ipams; +use PVE::API2::Network::SDN::Dns; use base qw(PVE::RESTHandler); @@ -45,6 +46,11 @@ __PACKAGE__->register_method ({ path => 'ipams', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Dns", + path => 'dns', +}); + __PACKAGE__->register_method({ name => 'index', path => '', @@ -76,6 +82,7 @@ __PACKAGE__->register_method({ { id => 'controllers' }, { id => 'subnets' }, { id => 'ipams' }, + { id => 'dns' }, ]; return $res; diff --git a/PVE/API2/Network/SDN/Dns.pm b/PVE/API2/Network/SDN/Dns.pm new file mode 100644 index 0000000..ea26af3 --- /dev/null +++ b/PVE/API2/Network/SDN/Dns.pm @@ -0,0 +1,242 @@ +package PVE::API2::Network::SDN::Dns; + +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::Dns; +use PVE::Network::SDN::Dns::Plugin; +use PVE::Network::SDN::Dns::PowerdnsPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_dns_type_enum = PVE::Network::SDN::Dns::Plugin->lookup_types(); + +my $api_sdn_dns_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id)); + $scfg->{dns} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN dns index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + type => { + description => "Only list sdn dns of specific type", + type => 'string', + enum => $sdn_dns_type_enum, + optional => 1, + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { dns => { type => 'string'}, + type => { type => 'string'}, + }, + }, + links => [ { rel => 'child', href => "{dns}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + + my $cfg = PVE::Network::SDN::Dns::config(); + + my @sids = PVE::Network::SDN::Dns::sdn_dns_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/dns/$id", $privs, 1); + + my $scfg = &$api_sdn_dns_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{dns}', + method => 'GET', + description => "Read sdn dns configuration.", + permissions => { + check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + dns => get_standard_option('pve-sdn-dns-id'), + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = PVE::Network::SDN::Dns::config(); + + return &$api_sdn_dns_config($cfg, $param->{dns}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn dns object.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Dns::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'dns'); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id, 1)) { + die "sdn dns object ID '$id' already defined\n"; + } + + $dns_cfg->{ids}->{$id} = $opts; + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + $plugin->on_update_hook($opts); + + PVE::Network::SDN::Dns::write_config($dns_cfg); + + }, "create sdn dns object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{dns}', + method => 'PUT', + description => "Update sdn dns object configuration.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'dns'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + PVE::SectionConfig::assert_if_modified($dns_cfg, $digest); + + my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } + + $plugin->on_update_hook($scfg); + + PVE::Network::SDN::Dns::write_config($dns_cfg); + + }, "update sdn dns object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{dns}', + method => 'DELETE', + description => "Delete sdn dns object configuration.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + dns => get_standard_option('pve-sdn-dns-id', { + completion => \&PVE::Network::SDN::Dns::complete_sdn_dns, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'dns'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Dns::write_config($cfg); + + }, "delete sdn dns object failed"); + + return undef; + }}); + +1; diff --git a/PVE/API2/Network/SDN/Makefile b/PVE/API2/Network/SDN/Makefile index 1117dfa..3683fa4 100644 --- a/PVE/API2/Network/SDN/Makefile +++ b/PVE/API2/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm +SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm PERL5DIR=${DESTDIR}/usr/share/perl5 diff --git a/PVE/Network/SDN/Dns.pm b/PVE/Network/SDN/Dns.pm new file mode 100644 index 0000000..c2e153a --- /dev/null +++ b/PVE/Network/SDN/Dns.pm @@ -0,0 +1,57 @@ +package PVE::Network::SDN::Dns; + +use strict; +use warnings; + +use Data::Dumper; +use JSON; + +use PVE::Tools qw(extract_param dir_glob_regex run_command); +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Network; + +use PVE::Network::SDN::Dns::PowerdnsPlugin; +use PVE::Network::SDN::Dns::Plugin; + +PVE::Network::SDN::Dns::PowerdnsPlugin->register(); +PVE::Network::SDN::Dns::Plugin->init(); + + +sub sdn_dns_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn dns ID specified\n" if !$id; + + my $scfg = $cfg->{ids}->{$id}; + die "sdn '$id' does not exist\n" if (!$noerr && !$scfg); + + return $scfg; +} + +sub config { + my $config = cfs_read_file("sdn/dns.cfg"); + return $config; +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/dns.cfg", $cfg); +} + +sub sdn_dns_ids { + my ($cfg) = @_; + + return keys %{$cfg->{ids}}; +} + +sub complete_sdn_dns { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::Dns::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Dns::sdn_dns_ids($cfg) ]; +} + +1; + diff --git a/PVE/Network/SDN/Dns/Makefile b/PVE/Network/SDN/Dns/Makefile new file mode 100644 index 0000000..81cd2a1 --- /dev/null +++ b/PVE/Network/SDN/Dns/Makefile @@ -0,0 +1,8 @@ +SOURCES=Plugin.pm PowerdnsPlugin.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Dns/$$i; done diff --git a/PVE/Network/SDN/Dns/Plugin.pm b/PVE/Network/SDN/Dns/Plugin.pm new file mode 100644 index 0000000..baa9316 --- /dev/null +++ b/PVE/Network/SDN/Dns/Plugin.pm @@ -0,0 +1,117 @@ +package PVE::Network::SDN::Dns::Plugin; + +use strict; +use warnings; + +use PVE::Tools qw(run_command); +use PVE::JSONSchema; +use PVE::Cluster; +use HTTP::Request; +use LWP::UserAgent; +use JSON; + +use Data::Dumper; +use PVE::JSONSchema qw(get_standard_option); +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file('sdn/dns.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }); + +PVE::JSONSchema::register_standard_option('pve-sdn-dns-id', { + description => "The SDN dns object identifier.", + type => 'string', format => 'pve-sdn-dns-id', +}); + +PVE::JSONSchema::register_format('pve-sdn-dns-id', \&parse_sdn_dns_id); +sub parse_sdn_dns_id { + my ($id, $noerr) = @_; + + if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { + return undef if $noerr; + die "dns ID '$id' contains illegal characters\n"; + } + return $id; +} + +my $defaultData = { + + propertyList => { + type => { + description => "Plugin type.", + type => 'string', format => 'pve-configid', + }, + ttl => { type => 'integer', optional => 1 }, + dns => get_standard_option('pve-sdn-dns-id', + { completion => \&PVE::Network::SDN::Dns::complete_sdn_dns }), + }, +}; + +sub private { + return $defaultData; +} + +sub parse_section_header { + my ($class, $line) = @_; + + if ($line =~ m/^(\S+):\s*(\S+)\s*$/) { + my ($type, $id) = (lc($1), $2); + my $errmsg = undef; # set if you want to skip whole section + eval { PVE::JSONSchema::pve_verify_configid($type); }; + $errmsg = $@ if $@; + my $config = {}; # to return additional attributes + return ($type, $id, $errmsg, $config); + } + return undef; +} + + +sub add_a_record { + my ($class, $plugin_config, $type, $zone, $reversezone, $hostname, $ip) = @_; +} + +sub del_a_record { + my ($class, $plugin_config, $hostname, $ip) = @_; +} + +sub on_update_hook { + my ($class, $plugin_config) = @_; +} + +#helpers +sub api_request { + my ($method, $url, $headers, $data) = @_; + + my $encoded_data = to_json($data) if $data; + + my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); + + my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30); + my $proxy = undef; + + if ($proxy) { + $ua->proxy(['http', 'https'], $proxy); + } else { + $ua->env_proxy; + } + + $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00); + + my $response = $ua->request($req); + my $code = $response->code; + + if ($code !~ /^2(\d+)$/) { + my $msg = $response->message || 'unknown'; + die "Invalid response from server: $code $msg\n"; + } + + my $raw = ''; + if (defined($response->decoded_content)) { + $raw = $response->decoded_content; + } else { + $raw = $response->content; + } + return from_json($raw) if $raw ne ''; +} + +1; diff --git a/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm new file mode 100644 index 0000000..8c5dd90 --- /dev/null +++ b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm @@ -0,0 +1,201 @@ +package PVE::Network::SDN::Dns::PowerdnsPlugin; + +use strict; +use warnings; +use PVE::INotify; +use PVE::Cluster; +use PVE::Tools; +use JSON; +use Net::IP; + +use base('PVE::Network::SDN::Dns::Plugin'); + +sub type { + return 'powerdns'; +} + +sub properties { + return { + url => { + type => 'string', + }, + key => { + type => 'string', + }, + }; +} + +sub options { + + return { + url => { optional => 0}, + key => { optional => 0 }, + ttl => { optional => 1 }, + }; +} + +# Plugin implementation + +sub add_a_record { + my ($class, $plugin_config, $zone, $hostname, $ip) = @_; + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + + my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; + my $fqdn = $hostname.".".$zone."."; + + + my $record = { content => $ip, + disabled => JSON::false, + name => $fqdn, + type => $type, + priority => 0 }; + + my $rrset = { name => $fqdn, + type => $type, + ttl => $ttl, + changetype => "REPLACE", + records => [ $record ] }; + + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error add $fqdn to zone $zone: $@"; + } +} + +sub add_ptr_record { + my ($class, $plugin_config, $zone, $hostname, $ip) = @_; + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + + my $reverseip = join(".", reverse(split(/\./, $ip)))."in-addr.arpa."; + my $fqdn = $hostname.".".$zone."."; + my $type = "PTR"; + + my $record = { content => $fqdn, + disabled => JSON::false, + name => $reverseip, + type => $type, + priority => 0 }; + + my $rrset = { name => $reverseip, + type => $type, + ttl => $ttl, + changetype => "REPLACE", + records => [ $record ] }; + + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error add $reverseip to zone $zone: $@"; + } +} + +sub del_a_record { + my ($class, $plugin_config, $zone, $hostname) = @_; + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + my $fqdn = $hostname.".".$zone."."; + my $type = "PTR"; + + my $rrset = { name => $fqdn, + type => $type, + changetype => "DELETE", + records => [] }; + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error delete $fqdn from zone $zone: $@"; + } +} + +sub del_ptr_record { + my ($class, $plugin_config, $zone, $ip) = @_; + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + + my $reverseip = join(".", reverse(split(/\./, $ip)))."in-addr.arpa."; + my $type = "PTR"; + + my $rrset = { name => $reverseip, + type => $type, + changetype => "DELETE", + records => [] }; + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error delete $reverseip from zone $zone: $@"; + } +} + +sub verify_zone { + my ($class, $plugin_config, $zone) = @_; + + #verify that api is working + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("GET", "$url/zones/$zone", $headers); + }; + + if ($@) { + die "can't read zone $zone: $@"; + } +} + + +sub on_update_hook { + my ($class, $plugin_config) = @_; + + #verify that api is working + + my $url = $plugin_config->{url}; + my $key = $plugin_config->{key}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key]; + + eval { + PVE::Network::SDN::Dns::Plugin::api_request("GET", "$url", $headers); + }; + + if ($@) { + die "dns api error: $@"; + } +} + +1; + + diff --git a/PVE/Network/SDN/Ipams/PVEPlugin.pm b/PVE/Network/SDN/Ipams/PVEPlugin.pm index 0dfc8a4..99af0ed 100644 --- a/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ b/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -99,7 +99,6 @@ sub add_next_freeip { while(1) { my $ip = $iplist->ip(); ++$iplist; - print "nextip: $ip\n"; next if defined($s->{ips}->{$ip}); $freeip = $ip; last; diff --git a/PVE/Network/SDN/Ipams/Plugin.pm b/PVE/Network/SDN/Ipams/Plugin.pm index fc736b8..683346c 100644 --- a/PVE/Network/SDN/Ipams/Plugin.pm +++ b/PVE/Network/SDN/Ipams/Plugin.pm @@ -110,7 +110,7 @@ sub api_request { my $response = $ua->request($req); my $code = $response->code; - if ($code !~ /2(\d+)$/) { + if ($code !~ /^2(\d+)$/) { my $msg = $response->message || 'unknown'; die "Invalid response from server: $code $msg\n"; } diff --git a/PVE/Network/SDN/Makefile b/PVE/Network/SDN/Makefile index fb68856..92cfcd0 100644 --- a/PVE/Network/SDN/Makefile +++ b/PVE/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm +SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm PERL5DIR=${DESTDIR}/usr/share/perl5 @@ -9,4 +9,5 @@ install: make -C Controllers install make -C Zones install make -C Ipams install + make -C Dns install diff --git a/PVE/Network/SDN/SubnetPlugin.pm b/PVE/Network/SDN/SubnetPlugin.pm index 6224065..3769e04 100644 --- a/PVE/Network/SDN/SubnetPlugin.pm +++ b/PVE/Network/SDN/SubnetPlugin.pm @@ -65,22 +65,25 @@ sub properties { type => 'string', description => "static routes [network=:gateway=,network=:gateway=,... ]", }, - #cloudinit, dhcp options - nameservers => { - type => 'string', format => 'address-list', - description => " dns nameserver", + dns => { + type => 'string', + description => "dns api server", }, - #cloudinit, dhcp options - searchdomain => { + reversedns => { type => 'string', + description => "reverse dns api server", }, - dhcp => { - type => 'boolean', - description => "enable dhcp for this subnet", + dnszone => { + type => 'string', + description => "dns domain zone ex: mydomain.com", }, - dns_driver => { + reversednszone => { type => 'string', - description => "Develop some dns registrations plugins (powerdns,...)", + description => "reverse dns zone ex: 0.168.192.in-addr.arpa", + }, + dnszoneprefix => { + type => 'string', + description => "dns domain zone prefix ex: 'adm' -> .adm.mydomain.com", }, ipam => { type => 'string', @@ -93,11 +96,12 @@ 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 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + reversednszone => { optional => 1 }, + dnszoneprefix => { optional => 1 }, ipam => { optional => 1 }, }; } @@ -105,12 +109,25 @@ sub options { sub on_update_hook { my ($class, $subnetid, $subnet_cfg) = @_; - my $subnet = $subnetid =~ s/-/\//r; - my $subnet_matcher = subnet_matcher($subnet); + my $cidr = $subnetid =~ s/-/\//r; + my $subnet_matcher = subnet_matcher($cidr); + + my $subnet = $subnet_cfg->{ids}->{$subnetid}; - my $gateway = $subnet_cfg->{ids}->{$subnetid}->{gateway}; + my $gateway = $subnet->{gateway}; + my $dns = $subnet->{dns}; + my $dnszone = $subnet->{dnszone}; + my $reversedns = $subnet->{reversedns}; + my $reversednszone = $subnet->{reversednszone}; + + #to: for /32 pointotoping, allow gateway outside the subnet raise_param_exc({ gateway => "$gateway is not in subnet $subnet"}) if $gateway && !$subnet_matcher->($gateway); + raise_param_exc({ dns => "missing dns provider"}) if $dnszone && !$dns; + raise_param_exc({ dnszone => "missing dns zone"}) if $dns && !$dnszone; + raise_param_exc({ reversedns => "missing dns provider"}) if $reversednszone && !$reversedns; + raise_param_exc({ reversednszone => "missing dns zone"}) if $reversedns && !$reversednszone; + } sub on_delete_hook { diff --git a/PVE/Network/SDN/Subnets.pm b/PVE/Network/SDN/Subnets.pm index 3ce2d44..4e8353e 100644 --- a/PVE/Network/SDN/Subnets.pm +++ b/PVE/Network/SDN/Subnets.pm @@ -5,8 +5,10 @@ use warnings; use Net::Subnet qw(subnet_matcher); use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use Net::IP; use PVE::Network::SDN::Ipams; +use PVE::Network::SDN::Dns; use PVE::Network::SDN::SubnetPlugin; PVE::Network::SDN::SubnetPlugin->register(); PVE::Network::SDN::SubnetPlugin->init(); @@ -75,41 +77,157 @@ sub find_ip_subnet { return ($subnetid, $subnet); } +my $verify_dns_zone = sub { + my ($zone, $dns) = @_; + + return if !$zone || !$dns; + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + my $plugin_config = $dns_cfg->{ids}->{$dns}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + $plugin->verify_zone($plugin_config, $zone); +}; + +my $add_dns_record = sub { + my ($zone, $dns, $hostname, $dnszoneprefix, $ip, $reverse) = @_; + + return if !$zone || !$dns || !$hostname || !$ip; + + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + my $plugin_config = $dns_cfg->{ids}->{$dns}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + if($reverse) { + $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip); + } else { + $plugin->add_a_record($plugin_config, $zone, $hostname, $ip); + } +}; + +my $del_dns_record = sub { + my ($zone, $dns, $hostname, $dnszoneprefix, $ip, $reverse) = @_; + + return if !$zone || !$dns || !$hostname || !$ip; + + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + my $plugin_config = $dns_cfg->{ids}->{$dns}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + if($reverse) { + $plugin->del_ptr_record($plugin_config, $zone, $ip); + } else { + $plugin->del_a_record($plugin_config, $zone, $hostname); + } +}; + sub next_free_ip { - my ($subnetid, $subnet) = @_; + my ($subnetid, $subnet, $hostname) = @_; + + my $cidr = undef; + my $ip = undef; my $ipamid = $subnet->{ipam}; - return if !$ipamid; + my $dns = $subnet->{dns}; + my $dnszone = $subnet->{dnszone}; + my $reversedns = $subnet->{reversedns}; + my $reversednszone = $subnet->{reversednszone}; + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + #verify dns zones before ipam + &$verify_dns_zone($dnszone, $dns); + &$verify_dns_zone($reversednszone, $reversedns); + + if($ipamid) { + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet); + ($ip, undef) = split(/\//, $cidr); + } - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - my $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet); - return $ip; + eval { + #add dns + &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip); + #add reverse dns + &$add_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1); + }; + if ($@) { + #rollback + my $err = $@; + eval { + PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname) + }; + die $err; + } + return $cidr; } sub add_ip { - my ($subnetid, $subnet, $ip) = @_; + my ($subnetid, $subnet, $ip, $hostname) = @_; my $ipamid = $subnet->{ipam}; - return if !$ipamid; + my $dns = $subnet->{dns}; + my $dnszone = $subnet->{dnszone}; + my $reversedns = $subnet->{reversedns}; + my $reversednszone = $subnet->{reversednszone}; + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + #verify dns zones before ipam + &$verify_dns_zone($dnszone, $dns); + &$verify_dns_zone($reversednszone, $reversedns); + + if ($ipamid) { + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->add_ip($plugin_config, $subnetid, $ip); + } - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - $plugin->add_ip($plugin_config, $subnetid, $ip); + eval { + #add dns + &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip); + #add reverse dns + &$add_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1); + }; + if ($@) { + #rollback + my $err = $@; + eval { + PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname) + }; + die $err; + } } sub del_ip { - my ($subnetid, $subnet, $ip) = @_; + my ($subnetid, $subnet, $ip, $hostname) = @_; my $ipamid = $subnet->{ipam}; - return if !$ipamid; + my $dns = $subnet->{dns}; + my $dnszone = $subnet->{dnszone}; + my $reversedns = $subnet->{reversedns}; + my $reversednszone = $subnet->{reversednszone}; + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + &$verify_dns_zone($dnszone, $dns); + &$verify_dns_zone($reversednszone, $reversedns); + + if ($ipamid) { + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->del_ip($plugin_config, $subnetid, $ip); + } - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $plugin_config = $ipam_cfg->{ids}->{$ipamid}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - $plugin->del_ip($plugin_config, $subnetid, $ip); + eval { + &$del_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip); + &$del_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1); + }; + if ($@) { + warn $@; + } } 1; diff --git a/PVE/Network/SDN/Vnets.pm b/PVE/Network/SDN/Vnets.pm index 6ea3a9a..c9916b1 100644 --- a/PVE/Network/SDN/Vnets.pm +++ b/PVE/Network/SDN/Vnets.pm @@ -55,7 +55,7 @@ sub get_vnet { } sub get_next_free_ip { - my ($vnet, $ipversion) = @_; + my ($vnet, $hostname, $ipversion) = @_; $ipversion = 4 if !$ipversion; my $subnets_cfg = PVE::Network::SDN::Subnets::config(); @@ -71,7 +71,7 @@ sub get_next_free_ip { $subnet = $subnets_cfg->{ids}->{$subnetid}; if ($subnet && $subnet->{ipam}) { eval { - $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet); + $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet, $hostname); }; warn $@ if $@; } @@ -83,23 +83,23 @@ sub get_next_free_ip { } sub add_ip { - my ($vnet, $cidr, $name) = @_; + my ($vnet, $cidr, $hostname) = @_; my ($ip, $mask) = split(/\//, $cidr); my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets}); return if !$subnet->{ipam}; - PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip); + PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip, $hostname); } sub del_ip { - my ($vnet, $cidr) = @_; + my ($vnet, $cidr, $hostname) = @_; my ($ip, $mask) = split(/\//, $cidr); my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets}); return if !$subnet->{ipam}; - PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip); + PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname); } 1; -- 2.20.1