From: Wolfgang Link Date: Tue, 31 Mar 2020 07:01:06 +0000 (+0200) Subject: Add DNSChallenge Plugin X-Git-Url: https://git.proxmox.com/?p=proxmox-acme.git;a=commitdiff_plain;h=98b96d9ee7753917d998026bbeed50eae64d30d2 Add DNSChallenge Plugin This plugin calls the custom script acme.sh and uses the implementation of the DNS API. Signed-off-by: Wolfgang Link --- diff --git a/debian/control b/debian/control index 87ba731..bb85c98 100644 --- a/debian/control +++ b/debian/control @@ -13,5 +13,6 @@ Description: easy and small shell script to automatically issue and renew the free certificates from Let's Encrypt. Depends: curl (>= 7.64.0-1), coreutils (>= 8.30-1), - sed (>= 4.7-1) + sed (>= 4.7-1), + libpve-common-perl, Recommends: idn diff --git a/src/Makefile b/src/Makefile index b65e330..aff47b5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -107,6 +107,7 @@ LIB_SOURCES = \ ACME.pm \ ACME/Challenge.pm \ ACME/StandAlone.pm \ + ACME/DNSChallenge.pm \ all: diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm index 114eb41..46b8fb1 100644 --- a/src/PVE/ACME.pm +++ b/src/PVE/ACME.pm @@ -23,6 +23,8 @@ file_set_contents file_get_contents ); +use PVE::ACME::DNSChallenge; + Crypt::OpenSSL::RSA->import_random_seed(); my $LETSENCRYPT_STAGING = 'https://acme-staging-v02.api.letsencrypt.org/directory'; diff --git a/src/PVE/ACME/DNSChallenge.pm b/src/PVE/ACME/DNSChallenge.pm new file mode 100644 index 0000000..7af442e --- /dev/null +++ b/src/PVE/ACME/DNSChallenge.pm @@ -0,0 +1,198 @@ +package PVE::ACME::DNSChallenge; + +use strict; +use warnings; + +use Digest::SHA qw(sha256); +use PVE::Tools; + +use base qw(PVE::ACME::Challenge); + +my $ACME_PATH = '/usr/share/proxmox-acme/proxmox-acme'; + +sub supported_challenge_types { + return { 'dns-01' => 1 }; +} + +sub type { + return 'dns'; +} + +my $api_name_list = [ + 'acmedns', + 'acmeproxy', + 'active24', + 'ad', + 'ali', + 'autodns', + 'aws', + 'azure', + 'cf', + 'clouddns', + 'cloudns', + 'cn', + 'conoha', + 'constellix', + 'cx', + 'cyon', + 'da', + 'ddnss', + 'desec', + 'dgon', + 'dnsimple', + 'do', + 'doapi', + 'domeneshop', + 'dp', + 'dpi', + 'dreamhost', + 'duckdns', + 'durabledns', + 'dyn', + 'dynu', + 'dynv6', + 'easydns', + 'euserv', + 'exoscale', + 'freedns', + 'gandi_livedns', + 'gcloud', + 'gd', + 'gdnsdk', + 'he', + 'hexonet', + 'hostingde', + 'infoblox', + 'internetbs', + 'inwx', + 'ispconfig', + 'jd', + 'kas', + 'kinghost', + 'knot', + 'leaseweb', + 'lexicon', + 'linode', + 'linode_v4', + 'loopia', + 'lua', + 'maradns', + 'me', + 'miab', + 'misaka', + 'myapi', + 'mydevil', + 'mydnsjp', + 'namecheap', + 'namecom', + 'namesilo', + 'nederhost', + 'neodigit', + 'netcup', + 'nic', + 'nsd', + 'nsone', + 'nsupdate', + 'nw', + 'one', + 'online', + 'openprovider', + 'opnsense', + 'ovh', + 'pdns', + 'pleskxml', + 'pointhq', + 'rackspace', + 'rcode0', + 'regru', + 'schlundtech', + 'selectel', + 'servercow', + 'tele3', + 'ultra', + 'unoeuro', + 'variomedia', + 'vscale', + 'vultr', + 'yandex', + 'zilore', + 'zone', + 'zonomi', +]; + +sub properties { + return { + api => { + description => "API plugin name", + type => 'string', + enum => $api_name_list, + }, + data => { + type => 'string', + description => 'DNS plugin data.', + }, + }; +} + +sub options { + return { + api => {}, + data => {}, + nodes => { optional => 1 }, + disable => { optional => 1 }, + }; +} + +my $outfunc = sub { + my $line = shift; + print "$line\n"; +}; + +sub extract_challenge { + my ($self, $challenge) = @_; + + return PVE::ACME::Challenge->extract_challenge($challenge, 'dns-01'); +} + +# The order of the parameters passed to proxmox-acme is important +# proxmox-acme setup $plugin [$domain|$alias] $txtvalue $plugin_conf_string +sub setup { + my ($self, $data) = @_; + + die "No plugin data for DNSChallenge\n" if !defined($data->{plugin}); + my $domain = $data->{plugin}->{alias} ? $data->{plugin}->{alias} : $data->{domain}; + my $txtvalue = PVE::ACME::encode(sha256($data->{key_authorization})); + my $dnsplugin = $data->{plugin}->{api}; + my $plugin_conf_string = $data->{plugin}->{data}; + + # for security reasons, we execute the command as nobody + # we can't verify that the code of the DNSPlugins are harmless. + my $cmd = ["setpriv", "--reuid", "nobody", "--regid", "nogroup", "--clear-groups", "--"]; + push @$cmd, "/usr/bin/bash", $ACME_PATH, "setup", $dnsplugin, $domain; + push @$cmd, $txtvalue, $plugin_conf_string; + + PVE::Tools::run_command($cmd, outfunc => $outfunc); + print "Add TXT record: _acme-challenge.$domain\n"; +} + +# The order of the parameters passed to proxmox-acme is important +# proxmox-acme teardown $plugin [$domain|$alias] $txtvalue $plugin_conf_string +sub teardown { + my ($self, $data) = @_; + + die "No plugin data for DNSChallenge\n" if !defined($data->{plugin}); + my $domain = $data->{plugin}->{alias} ? $data->{plugin}->{alias} : $data->{domain}; + my $txtvalue = PVE::ACME::encode(sha256($data->{key_authorization})); + my $dnsplugin = $data->{plugin}->{api}; + my $plugin_conf_string = $data->{plugin}->{data}; + + # for security reasons, we execute the command as nobody + # we can't verify that the code of the DNSPlugins are harmless. + my $cmd = ["setpriv", "--reuid", "nobody", "--regid", "nogroup", "--clear-groups", "--"]; + push @$cmd, "/usr/bin/bash", "$ACME_PATH", "teardown", $dnsplugin, $domain ; + push @$cmd, $txtvalue, $plugin_conf_string; + PVE::Tools::run_command($cmd, outfunc => $outfunc); + print "Remove TXT record: _acme-challenge.$domain\n"; +} + +1;