]> git.proxmox.com Git - proxmox-acme.git/blob - src/PVE/ACME/DNSChallenge.pm
DNS Challenge: add validation-delay plugin option
[proxmox-acme.git] / src / PVE / ACME / DNSChallenge.pm
1 package PVE::ACME::DNSChallenge;
2
3 use strict;
4 use warnings;
5
6 use Digest::SHA qw(sha256);
7 use PVE::Tools;
8
9 use base qw(PVE::ACME::Challenge);
10
11 my $ACME_PATH = '/usr/share/proxmox-acme/proxmox-acme';
12
13 sub supported_challenge_types {
14 return ["dns-01"];
15 }
16
17 sub type {
18 return 'dns';
19 }
20
21 my $plugin_names = [
22 'acmedns',
23 'acmeproxy',
24 'active24',
25 'ad',
26 'ali',
27 'autodns',
28 'aws',
29 'azure',
30 'cf',
31 'clouddns',
32 'cloudns',
33 'cn',
34 'conoha',
35 'constellix',
36 'cx',
37 'cyon',
38 'da',
39 'ddnss',
40 'desec',
41 'dgon',
42 'dnsimple',
43 'do',
44 'doapi',
45 'domeneshop',
46 'dp',
47 'dpi',
48 'dreamhost',
49 'duckdns',
50 'durabledns',
51 'dyn',
52 'dynu',
53 'dynv6',
54 'easydns',
55 'euserv',
56 'exoscale',
57 'freedns',
58 'gandi_livedns',
59 'gcloud',
60 'gd',
61 'gdnsdk',
62 'he',
63 'hexonet',
64 'hostingde',
65 'infoblox',
66 'internetbs',
67 'inwx',
68 'ispconfig',
69 'jd',
70 'kas',
71 'kinghost',
72 'knot',
73 'leaseweb',
74 'lexicon',
75 'linode',
76 'linode_v4',
77 'loopia',
78 'lua',
79 'maradns',
80 'me',
81 'miab',
82 'misaka',
83 'myapi',
84 'mydevil',
85 'mydnsjp',
86 'namecheap',
87 'namecom',
88 'namesilo',
89 'nederhost',
90 'neodigit',
91 'netcup',
92 'nic',
93 'nsd',
94 'nsone',
95 'nsupdate',
96 'nw',
97 'one',
98 'online',
99 'openprovider',
100 'opnsense',
101 'ovh',
102 'pdns',
103 'pleskxml',
104 'pointhq',
105 'rackspace',
106 'rcode0',
107 'regru',
108 'schlundtech',
109 'selectel',
110 'servercow',
111 'tele3',
112 'ultra',
113 'unoeuro',
114 'variomedia',
115 'vscale',
116 'vultr',
117 'yandex',
118 'zilore',
119 'zone',
120 'zonomi',
121 ];
122 sub get_supported_plugins {
123 return $plugin_names;
124 }
125
126 sub properties {
127 return {
128 api => {
129 description => "API plugin name",
130 type => 'string',
131 enum => $plugin_names,
132 },
133 data => {
134 type => 'string',
135 description => 'DNS plugin data.',
136 },
137 'validation-delay' => {
138 type => 'integer',
139 description => 'Extra delay in seconds to wait before requesting validation.'
140 .' Allows to cope with a long TTL of DNS records.',
141 # low default, but our bet is that the acme-challenge domain isn't
142 # cached at all, so it hopefully shouldn't run into TTL issues
143 default => 30,
144 optional => 1,
145 minimum => 0,
146 maximum => 2 * 24 * 60 * 60,
147 }
148 };
149 }
150
151 sub options {
152 return {
153 api => {},
154 data => { optional => 1 },
155 nodes => { optional => 1 },
156 disable => { optional => 1 },
157 'validation-delay' => { optional => 1 },
158 };
159 }
160
161 my $proxmox_acme_command = sub {
162 my ($self, $acme, $auth, $data, $action) = @_;
163
164 die "No plugin data for DNSChallenge\n" if !defined($data->{plugin});
165
166 my $alias = $data->{alias};
167 my $domain = $auth->{identifier}->{value};
168
169 my $challenge = $self->extract_challenge($auth->{challenges});
170 my $key_auth = $acme->key_authorization($challenge->{token});
171
172 my $txtvalue = PVE::ACME::encode(sha256($key_auth));
173 my $dnsplugin = $data->{plugin}->{api};
174 my $plugin_conf_string = $data->{plugin}->{data};
175
176 # for security reasons, we execute the command as nobody
177 # we can't verify that the code of the DNSPlugins are harmless.
178 my $cmd = ["setpriv", "--reuid", "nobody", "--regid", "nogroup", "--clear-groups", "--reset-env", "--"];
179
180 # The order of the parameters passed to proxmox-acme is important
181 # proxmox-acme <setup|teardown> $plugin <$domain|$alias> $txtvalue [$plugin_conf_string]
182 push @$cmd, "/bin/bash", $ACME_PATH, $action, $dnsplugin;
183 if ($alias) {
184 push @$cmd, $alias;
185 } else {
186 push @$cmd, $domain;
187 }
188 my $input = "$txtvalue\n";
189 $input .= "$plugin_conf_string\n" if $plugin_conf_string;
190
191 PVE::Tools::run_command($cmd, input => $input);
192
193 $data->{url} = $challenge->{url};
194
195 return $domain;
196 };
197
198 sub setup {
199 my ($self, $acme, $auth, $data) = @_;
200
201 my $domain = $proxmox_acme_command->($self, $acme, $auth, $data, 'setup');
202 print "Add TXT record: _acme-challenge.$domain\n";
203
204 # FIXME: probe ourself for propagation of TXT record, while not 100%
205 # failsafe it's good enough of a heuristic to do away with fixed sleep
206 # intervalls - original acme.sh employs that heuristic too.
207 my $delay = $data->{'validation-delay'} // 30;
208 if ($delay > 0) {
209 print "Sleeping $delay seconds to wait for TXT record propagation\n";
210 sleep($delay); # don't care for EINTR
211 }
212 }
213
214 sub teardown {
215 my ($self, $acme, $auth, $data) = @_;
216
217 my $domain = $proxmox_acme_command->($self, $acme, $auth, $data, 'teardown');
218 print "Remove TXT record: _acme-challenge.$domain\n";
219 }
220
221 1;