]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/ACMEAccount.pm
1 package PVE
::API2
::ACMEAccount
;
8 use PVE
::Exception
qw(raise_param_exc);
9 use PVE
::JSONSchema
qw(get_standard_option);
10 use PVE
::RPCEnvironment
;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::ACME
::Challenge
;
14 use PVE
::API2
::ACMEPlugin
;
16 use base
qw(PVE::RESTHandler);
18 __PACKAGE__-
>register_method ({
19 subclass
=> "PVE::API2::ACMEPlugin",
23 my $acme_directories = [
25 name
=> 'Let\'s Encrypt V2',
26 url
=> 'https://acme-v02.api.letsencrypt.org/directory',
29 name
=> 'Let\'s Encrypt V2 Staging',
30 url
=> 'https://acme-staging-v02.api.letsencrypt.org/directory',
33 my $acme_default_directory_url = $acme_directories->[0]->{url
};
34 my $account_contact_from_param = sub {
35 my @addresses = PVE
::Tools
::split_list
(extract_param
($_[0], 'contact'));
36 return [ map { "mailto:$_" } @addresses ];
38 my $acme_account_dir = PVE
::CertHelpers
::acme_account_dir
();
40 __PACKAGE__-
>register_method ({
44 permissions
=> { user
=> 'all' },
45 description
=> "ACMEAccount index.",
47 additionalProperties
=> 0,
57 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
63 { name
=> 'account' },
66 { name
=> 'directories' },
67 { name
=> 'plugins' },
68 { name
=> 'challenge-schema' },
72 __PACKAGE__-
>register_method ({
73 name
=> 'account_index',
76 permissions
=> { user
=> 'all' },
77 description
=> "ACMEAccount index.",
80 additionalProperties
=> 0,
90 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
95 my $accounts = PVE
::CertHelpers
::list_acme_accounts
();
96 return [ map { { name
=> $_ } } @$accounts ];
99 __PACKAGE__-
>register_method ({
100 name
=> 'register_account',
103 description
=> "Register a new ACME account with CA.",
106 additionalProperties
=> 0,
108 name
=> get_standard_option
('pve-acme-account-name'),
109 contact
=> get_standard_option
('pve-acme-account-contact'),
112 description
=> 'URL of CA TermsOfService - setting this indicates agreement.',
115 directory
=> get_standard_option
('pve-acme-directory-url', {
116 default => $acme_default_directory_url,
121 description
=> 'Key Identifier for External Account Binding.',
122 requires
=> 'eab-hmac-key',
127 description
=> 'HMAC key for External Account Binding.',
128 requires
=> 'eab-kid',
139 my $rpcenv = PVE
::RPCEnvironment
::get
();
140 my $authuser = $rpcenv->get_user();
142 my $account_name = extract_param
($param, 'name') // 'default';
143 my $account_file = "${acme_account_dir}/${account_name}";
144 mkdir $acme_account_dir if ! -e
$acme_account_dir;
146 my $eab_kid = extract_param
($param, 'eab-kid');
147 my $eab_hmac_key = extract_param
($param, 'eab-hmac-key');
149 raise_param_exc
({'name' => "ACME account config file '${account_name}' already exists."})
152 my $directory = extract_param
($param, 'directory') // $acme_default_directory_url;
153 my $contact = $account_contact_from_param->($param);
156 PVE
::Cluster
::cfs_lock_acme
($account_name, 10, sub {
157 die "ACME account config file '${account_name}' already exists.\n"
160 my $acme = PVE
::ACME-
>new($account_file, $directory);
161 print "Generating ACME account key..\n";
163 print "Registering ACME account..\n";
165 my %info = (contact
=> $contact);
166 if (defined($eab_kid)) {
169 hmac_key
=> $eab_hmac_key
173 eval { $acme->new_account($param->{tos_url
}, %info); };
176 unlink $account_file;
177 die "Registration failed: $err\n";
179 print "Registration successful, account URL: '$acme->{location}'\n";
184 return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd);
187 my $update_account = sub {
188 my ($param, $msg, %info) = @_;
190 my $account_name = extract_param
($param, 'name') // 'default';
191 my $account_file = "${acme_account_dir}/${account_name}";
193 raise_param_exc
({'name' => "ACME account config file '${account_name}' does not exist."})
194 if ! -e
$account_file;
197 my $rpcenv = PVE
::RPCEnvironment
::get
();
198 my $authuser = $rpcenv->get_user();
201 PVE
::Cluster
::cfs_lock_acme
($account_name, 10, sub {
202 die "ACME account config file '${account_name}' does not exist.\n"
203 if ! -e
$account_file;
205 my $acme = PVE
::ACME-
>new($account_file);
207 $acme->update_account(%info);
208 if ($info{status
} && $info{status
} eq 'deactivated') {
209 my $deactivated_name;
211 my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}";
212 if (! -e
$candidate) {
213 $deactivated_name = $candidate;
217 if ($deactivated_name) {
218 print "Renaming account file from '$account_file' to '$deactivated_name'\n";
219 rename($account_file, $deactivated_name) or
220 warn ".. failed - $!\n";
222 warn "No free slot to rename deactivated account file '$account_file', leaving in place\n";
229 return $rpcenv->fork_worker("acme${msg}", undef, $authuser, $realcmd);
232 __PACKAGE__-
>register_method ({
233 name
=> 'update_account',
234 path
=> 'account/{name}',
236 description
=> "Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.",
239 additionalProperties
=> 0,
241 name
=> get_standard_option
('pve-acme-account-name'),
242 contact
=> get_standard_option
('pve-acme-account-contact', {
253 my $contact = $account_contact_from_param->($param);
254 if (scalar @$contact) {
255 return $update_account->($param, 'update', contact
=> $contact);
257 return $update_account->($param, 'refresh');
261 __PACKAGE__-
>register_method ({
262 name
=> 'get_account',
263 path
=> 'account/{name}',
265 description
=> "Return existing ACME account information.",
268 additionalProperties
=> 0,
270 name
=> get_standard_option
('pve-acme-account-name'),
275 additionalProperties
=> 0,
282 directory
=> get_standard_option
('pve-acme-directory-url', {
298 my $account_name = extract_param
($param, 'name') // 'default';
299 my $account_file = "${acme_account_dir}/${account_name}";
301 raise_param_exc
({'name' => "ACME account config file '${account_name}' does not exist."})
302 if ! -e
$account_file;
304 my $acme = PVE
::ACME-
>new($account_file);
308 $res->{account
} = $acme->{account
};
309 $res->{directory
} = $acme->{directory
};
310 $res->{location
} = $acme->{location
};
311 $res->{tos
} = $acme->{tos
};
316 __PACKAGE__-
>register_method ({
317 name
=> 'deactivate_account',
318 path
=> 'account/{name}',
320 description
=> "Deactivate existing ACME account at CA.",
323 additionalProperties
=> 0,
325 name
=> get_standard_option
('pve-acme-account-name'),
334 return $update_account->($param, 'deactivate', status
=> 'deactivated');
337 # TODO: deprecated, remove with pve 9
338 __PACKAGE__-
>register_method ({
342 description
=> "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.",
343 permissions
=> { user
=> 'all' },
345 additionalProperties
=> 0,
347 directory
=> get_standard_option
('pve-acme-directory-url', {
348 default => $acme_default_directory_url,
356 description
=> 'ACME TermsOfService URL.',
361 my $directory = extract_param
($param, 'directory') // $acme_default_directory_url;
363 my $acme = PVE
::ACME-
>new(undef, $directory);
364 my $meta = $acme->get_meta();
366 return $meta ?
$meta->{termsOfService
} : undef;
369 __PACKAGE__-
>register_method ({
373 description
=> "Retrieve ACME Directory Meta Information",
374 permissions
=> { user
=> 'all' },
376 additionalProperties
=> 0,
378 directory
=> get_standard_option
('pve-acme-directory-url', {
379 default => $acme_default_directory_url,
386 additionalProperties
=> 1,
391 description
=> 'ACME TermsOfService URL.',
393 externalAccountRequired
=> {
396 description
=> 'EAB Required'
401 description
=> 'URL to more information about the ACME server.'
406 description
=> 'Hostnames referring to the ACME servers.'
413 my $directory = extract_param
($param, 'directory') // $acme_default_directory_url;
415 my $acme = PVE
::ACME-
>new(undef, $directory);
416 my $meta = $acme->get_meta();
421 __PACKAGE__-
>register_method ({
422 name
=> 'get_directories',
423 path
=> 'directories',
425 description
=> "Get named known ACME directory endpoints.",
426 permissions
=> { user
=> 'all' },
428 additionalProperties
=> 0,
435 additionalProperties
=> 0,
440 url
=> get_standard_option
('pve-acme-directory-url'),
447 return $acme_directories;
450 __PACKAGE__-
>register_method ({
451 name
=> 'challengeschema',
452 path
=> 'challenge-schema',
454 description
=> "Get schema of ACME challenge types.",
455 permissions
=> { user
=> 'all' },
457 additionalProperties
=> 0,
464 additionalProperties
=> 0,
470 description
=> 'Human readable name, falls back to id',
485 my $plugin_type_enum = PVE
::ACME
::Challenge-
>lookup_types();
489 for my $type (@$plugin_type_enum) {
490 my $plugin = PVE
::ACME
::Challenge-
>lookup($type);
491 next if !$plugin->can('get_supported_plugins');
493 my $plugin_type = $plugin->type();
494 my $plugins = $plugin->get_supported_plugins();
495 for my $id (sort keys %$plugins) {
496 my $schema = $plugins->{$id};
499 name
=> $schema->{name
} // $id,
500 type
=> $plugin_type,