]>
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' },
65 { name
=> 'directories' },
66 { name
=> 'plugins' },
67 { name
=> 'challenge-schema' },
71 __PACKAGE__-
>register_method ({
72 name
=> 'account_index',
75 permissions
=> { user
=> 'all' },
76 description
=> "ACMEAccount index.",
79 additionalProperties
=> 0,
89 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
94 my $accounts = PVE
::CertHelpers
::list_acme_accounts
();
95 return [ map { { name
=> $_ } } @$accounts ];
98 __PACKAGE__-
>register_method ({
99 name
=> 'register_account',
102 description
=> "Register a new ACME account with CA.",
105 additionalProperties
=> 0,
107 name
=> get_standard_option
('pve-acme-account-name'),
108 contact
=> get_standard_option
('pve-acme-account-contact'),
111 description
=> 'URL of CA TermsOfService - setting this indicates agreement.',
114 directory
=> get_standard_option
('pve-acme-directory-url', {
115 default => $acme_default_directory_url,
126 my $rpcenv = PVE
::RPCEnvironment
::get
();
127 my $authuser = $rpcenv->get_user();
129 my $account_name = extract_param
($param, 'name') // 'default';
130 my $account_file = "${acme_account_dir}/${account_name}";
131 mkdir $acme_account_dir if ! -e
$acme_account_dir;
133 raise_param_exc
({'name' => "ACME account config file '${account_name}' already exists."})
136 my $directory = extract_param
($param, 'directory') // $acme_default_directory_url;
137 my $contact = $account_contact_from_param->($param);
140 PVE
::Cluster
::cfs_lock_acme
($account_name, 10, sub {
141 die "ACME account config file '${account_name}' already exists.\n"
144 my $acme = PVE
::ACME-
>new($account_file, $directory);
145 print "Generating ACME account key..\n";
147 print "Registering ACME account..\n";
148 eval { $acme->new_account($param->{tos_url
}, contact
=> $contact); };
150 unlink $account_file;
151 die "Registration failed: $err\n";
153 print "Registration successful, account URL: '$acme->{location}'\n";
158 return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd);
161 my $update_account = sub {
162 my ($param, $msg, %info) = @_;
164 my $account_name = extract_param
($param, 'name') // 'default';
165 my $account_file = "${acme_account_dir}/${account_name}";
167 raise_param_exc
({'name' => "ACME account config file '${account_name}' does not exist."})
168 if ! -e
$account_file;
171 my $rpcenv = PVE
::RPCEnvironment
::get
();
172 my $authuser = $rpcenv->get_user();
175 PVE
::Cluster
::cfs_lock_acme
($account_name, 10, sub {
176 die "ACME account config file '${account_name}' does not exist.\n"
177 if ! -e
$account_file;
179 my $acme = PVE
::ACME-
>new($account_file);
181 $acme->update_account(%info);
182 if ($info{status
} && $info{status
} eq 'deactivated') {
183 my $deactivated_name;
185 my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}";
186 if (! -e
$candidate) {
187 $deactivated_name = $candidate;
191 if ($deactivated_name) {
192 print "Renaming account file from '$account_file' to '$deactivated_name'\n";
193 rename($account_file, $deactivated_name) or
194 warn ".. failed - $!\n";
196 warn "No free slot to rename deactivated account file '$account_file', leaving in place\n";
203 return $rpcenv->fork_worker("acme${msg}", undef, $authuser, $realcmd);
206 __PACKAGE__-
>register_method ({
207 name
=> 'update_account',
208 path
=> 'account/{name}',
210 description
=> "Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.",
213 additionalProperties
=> 0,
215 name
=> get_standard_option
('pve-acme-account-name'),
216 contact
=> get_standard_option
('pve-acme-account-contact', {
227 my $contact = $account_contact_from_param->($param);
228 if (scalar @$contact) {
229 return $update_account->($param, 'update', contact
=> $contact);
231 return $update_account->($param, 'refresh');
235 __PACKAGE__-
>register_method ({
236 name
=> 'get_account',
237 path
=> 'account/{name}',
239 description
=> "Return existing ACME account information.",
242 additionalProperties
=> 0,
244 name
=> get_standard_option
('pve-acme-account-name'),
249 additionalProperties
=> 0,
256 directory
=> get_standard_option
('pve-acme-directory-url', {
272 my $account_name = extract_param
($param, 'name') // 'default';
273 my $account_file = "${acme_account_dir}/${account_name}";
275 raise_param_exc
({'name' => "ACME account config file '${account_name}' does not exist."})
276 if ! -e
$account_file;
278 my $acme = PVE
::ACME-
>new($account_file);
282 $res->{account
} = $acme->{account
};
283 $res->{directory
} = $acme->{directory
};
284 $res->{location
} = $acme->{location
};
285 $res->{tos
} = $acme->{tos
};
290 __PACKAGE__-
>register_method ({
291 name
=> 'deactivate_account',
292 path
=> 'account/{name}',
294 description
=> "Deactivate existing ACME account at CA.",
297 additionalProperties
=> 0,
299 name
=> get_standard_option
('pve-acme-account-name'),
308 return $update_account->($param, 'deactivate', status
=> 'deactivated');
311 __PACKAGE__-
>register_method ({
315 description
=> "Retrieve ACME TermsOfService URL from CA.",
316 permissions
=> { user
=> 'all' },
318 additionalProperties
=> 0,
320 directory
=> get_standard_option
('pve-acme-directory-url', {
321 default => $acme_default_directory_url,
329 description
=> 'ACME TermsOfService URL.',
334 my $directory = extract_param
($param, 'directory') // $acme_default_directory_url;
336 my $acme = PVE
::ACME-
>new(undef, $directory);
337 my $meta = $acme->get_meta();
339 return $meta ?
$meta->{termsOfService
} : undef;
342 __PACKAGE__-
>register_method ({
343 name
=> 'get_directories',
344 path
=> 'directories',
346 description
=> "Get named known ACME directory endpoints.",
347 permissions
=> { user
=> 'all' },
349 additionalProperties
=> 0,
356 additionalProperties
=> 0,
361 url
=> get_standard_option
('pve-acme-directory-url'),
368 return $acme_directories;
371 __PACKAGE__-
>register_method ({
372 name
=> 'challengeschema',
373 path
=> 'challenge-schema',
375 description
=> "Get schema of ACME challenge types.",
376 permissions
=> { user
=> 'all' },
378 additionalProperties
=> 0,
385 additionalProperties
=> 0,
391 description
=> 'Human readable name, falls back to id',
406 my $plugin_type_enum = PVE
::ACME
::Challenge-
>lookup_types();
410 for my $type (@$plugin_type_enum) {
411 my $plugin = PVE
::ACME
::Challenge-
>lookup($type);
412 next if !$plugin->can('get_supported_plugins');
414 my $plugin_type = $plugin->type();
415 my $plugins = $plugin->get_supported_plugins();
416 for my $id (sort keys %$plugins) {
417 my $schema = $plugins->{$id};
420 name
=> $schema->{name
} // $id,
421 type
=> $plugin_type,