]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/ACMEPlugin.pm
api: backup: update: check permissions of delete params too
[pve-manager.git] / PVE / API2 / ACMEPlugin.pm
1 package PVE::API2::ACMEPlugin;
2
3 use strict;
4 use warnings;
5
6 use MIME::Base64;
7 use Storable qw(dclone);
8
9 use PVE::ACME::Challenge;
10 use PVE::ACME::DNSChallenge;
11 use PVE::ACME::StandAlone;
12 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file);
13 use PVE::JSONSchema qw(register_standard_option get_standard_option);
14 use PVE::Tools qw(extract_param);
15
16 use base qw(PVE::RESTHandler);
17
18 my $plugin_config_file = "priv/acme/plugins.cfg";
19
20 cfs_register_file($plugin_config_file,
21 sub { PVE::ACME::Challenge->parse_config(@_); },
22 sub { PVE::ACME::Challenge->write_config(@_); },
23 );
24
25 PVE::ACME::DNSChallenge->register();
26 PVE::ACME::StandAlone->register();
27 PVE::ACME::Challenge->init();
28
29 PVE::JSONSchema::register_standard_option('pve-acme-pluginid', {
30 type => 'string',
31 format => 'pve-configid',
32 description => 'Unique identifier for ACME plugin instance.',
33 });
34
35 my $plugin_type_enum = PVE::ACME::Challenge->lookup_types();
36
37 my $modify_cfg_for_api = sub {
38 my ($cfg, $pluginid) = @_;
39
40 die "ACME plugin '$pluginid' not defined\n" if !defined($cfg->{ids}->{$pluginid});
41
42 my $plugin_cfg = dclone($cfg->{ids}->{$pluginid});
43 $plugin_cfg->{plugin} = $pluginid;
44 $plugin_cfg->{digest} = $cfg->{digest};
45
46 return $plugin_cfg;
47 };
48
49 __PACKAGE__->register_method ({
50 name => 'index',
51 path => '',
52 method => 'GET',
53 permissions => {
54 check => ['perm', '/', [ 'Sys.Modify' ]],
55 },
56 description => "ACME plugin index.",
57 protected => 1,
58 parameters => {
59 additionalProperties => 0,
60 properties => {
61 type => {
62 description => "Only list ACME plugins of a specific type",
63 type => 'string',
64 enum => $plugin_type_enum,
65 optional => 1,
66 },
67 },
68 },
69 returns => {
70 type => 'array',
71 items => {
72 type => "object",
73 properties => {
74 plugin => get_standard_option('pve-acme-pluginid'),
75 },
76 },
77 links => [ { rel => 'child', href => "{plugin}" } ],
78 },
79 code => sub {
80 my ($param) = @_;
81
82 my $cfg = load_config();
83
84 my $res = [];
85 foreach my $pluginid (keys %{$cfg->{ids}}) {
86 my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid);
87 next if $param->{type} && $param->{type} ne $plugin_cfg->{type};
88 push @$res, $plugin_cfg;
89 }
90
91 return $res;
92 }
93 });
94
95 __PACKAGE__->register_method({
96 name => 'get_plugin_config',
97 path => '{id}',
98 method => 'GET',
99 description => "Get ACME plugin configuration.",
100 permissions => {
101 check => ['perm', '/', [ 'Sys.Modify' ]],
102 },
103 protected => 1,
104 parameters => {
105 additionalProperties => 0,
106 properties => {
107 id => get_standard_option('pve-acme-pluginid'),
108 },
109 },
110 returns => {
111 type => 'object',
112 },
113 code => sub {
114 my ($param) = @_;
115
116 my $cfg = load_config();
117 return $modify_cfg_for_api->($cfg, $param->{id});
118 }
119 });
120
121 __PACKAGE__->register_method({
122 name => 'add_plugin',
123 path => '',
124 method => 'POST',
125 description => "Add ACME plugin configuration.",
126 permissions => {
127 check => ['perm', '/', [ 'Sys.Modify' ]],
128 },
129 protected => 1,
130 parameters => PVE::ACME::Challenge->createSchema(),
131 returns => {
132 type => "null"
133 },
134 code => sub {
135 my ($param) = @_;
136
137 my $id = extract_param($param, 'id');
138 my $type = extract_param($param, 'type');
139
140 cfs_lock_file($plugin_config_file, undef, sub {
141 my $cfg = load_config();
142 die "ACME plugin ID '$id' already exists\n" if defined($cfg->{ids}->{$id});
143
144 my $plugin = PVE::ACME::Challenge->lookup($type);
145 my $opts = $plugin->check_config($id, $param, 1, 1);
146
147 $cfg->{ids}->{$id} = $opts;
148 $cfg->{ids}->{$id}->{type} = $type;
149
150 cfs_write_file($plugin_config_file, $cfg);
151 });
152 die "$@" if $@;
153
154 return undef;
155 }
156 });
157
158 __PACKAGE__->register_method({
159 name => 'update_plugin',
160 path => '{id}',
161 method => 'PUT',
162 description => "Update ACME plugin configuration.",
163 permissions => {
164 check => ['perm', '/', [ 'Sys.Modify' ]],
165 },
166 protected => 1,
167 parameters => PVE::ACME::Challenge->updateSchema(),
168 returns => {
169 type => "null"
170 },
171 code => sub {
172 my ($param) = @_;
173
174 my $id = extract_param($param, 'id');
175 my $delete = extract_param($param, 'delete');
176 my $digest = extract_param($param, 'digest');
177
178 cfs_lock_file($plugin_config_file, undef, sub {
179 my $cfg = load_config();
180 PVE::Tools::assert_if_modified($cfg->{digest}, $digest);
181 my $plugin_cfg = $cfg->{ids}->{$id};
182 die "ACME plugin ID '$id' does not exist\n" if !$plugin_cfg;
183
184 my $type = $plugin_cfg->{type};
185 my $plugin = PVE::ACME::Challenge->lookup($type);
186
187 if (defined($delete)) {
188 my $schema = $plugin->private();
189 my $options = $schema->{options}->{$type};
190 for my $k (PVE::Tools::split_list($delete)) {
191 my $d = $options->{$k} || die "no such option '$k'\n";
192 die "unable to delete required option '$k'\n" if !$d->{optional};
193
194 delete $cfg->{ids}->{$id}->{$k};
195 }
196 }
197
198 my $opts = $plugin->check_config($id, $param, 0, 1);
199 for my $k (sort keys %$opts) {
200 $plugin_cfg->{$k} = $opts->{$k};
201 }
202
203 cfs_write_file($plugin_config_file, $cfg);
204 });
205 die "$@" if $@;
206
207 return undef;
208 }
209 });
210
211 __PACKAGE__->register_method({
212 name => 'delete_plugin',
213 path => '{id}',
214 method => 'DELETE',
215 description => "Delete ACME plugin configuration.",
216 permissions => {
217 check => ['perm', '/', [ 'Sys.Modify' ]],
218 },
219 protected => 1,
220 parameters => {
221 additionalProperties => 0,
222 properties => {
223 id => get_standard_option('pve-acme-pluginid'),
224 },
225 },
226 returns => {
227 type => "null"
228 },
229 code => sub {
230 my ($param) = @_;
231
232 my $id = extract_param($param, 'id');
233
234 cfs_lock_file($plugin_config_file, undef, sub {
235 my $cfg = load_config();
236
237 delete $cfg->{ids}->{$id};
238
239 cfs_write_file($plugin_config_file, $cfg);
240 });
241 die "$@" if $@;
242
243 return undef;
244 }
245 });
246
247 sub load_config {
248 # auto-adds the standalone plugin if no config is there for backwards
249 # compatibility, so ALWAYS call the cfs registered parser
250 return cfs_read_file($plugin_config_file);
251 }
252
253 1;