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