]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/CLI/pmgconfig.pm
1 package PMG
::CLI
::pmgconfig
;
11 use PVE
::Tools
qw(extract_param);
14 use PVE
::JSONSchema
qw(get_standard_option);
16 use PMG
::RESTEnvironment
;
26 use PMG
::API2
::ACMEPlugin
;
27 use PMG
::API2
::Certificates
;
28 use PMG
::API2
::DKIMSign
;
30 use base
qw(PVE::CLIHandler);
32 my $nodename = PVE
::INotify
::nodename
();
34 sub setup_environment
{
35 PMG
::RESTEnvironment-
>setup_default_cli_env();
40 my $status = PVE
::Tools
::upid_read_status
($upid);
41 print "Task $status\n";
42 exit($status eq 'OK' ?
0 : -1);
48 my $load_file_and_encode = sub {
51 return PVE
::ACME
::Challenge-
>encode_value('string', 'data', PVE
::Tools
::file_get_contents
($filename));
55 'upload_custom_cert' => [
60 ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
63 ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
67 return $mapping->{$name};
70 __PACKAGE__-
>register_method ({
74 description
=> "Print configuration setting which can be used in templates.",
76 additionalProperties
=> 0,
79 returns
=> { type
=> 'null'},
83 my $cfg = PMG
::Config-
>new();
84 my $vars = $cfg->get_template_vars();
86 foreach my $realm (sort keys %$vars) {
87 foreach my $section (sort keys %{$vars->{$realm}}) {
88 my $secvalue = $vars->{$realm}->{$section} // '';
90 foreach my $key (sort keys %{$vars->{$realm}->{$section}}) {
91 my $value = $vars->{$realm}->{$section}->{$key} // '';
92 print "$realm.$section.$key = $value\n";
95 print "$realm.$section = $secvalue\n";
103 __PACKAGE__-
>register_method ({
107 description
=> "Synchronize Proxmox Mail Gateway configurations with system configuration.",
109 additionalProperties
=> 0,
112 description
=> "Restart services if necessary.",
119 returns
=> { type
=> 'null'},
123 my $cfg = PMG
::Config-
>new();
125 my $ruledb = PMG
::RuleDB-
>new();
126 my $rulecache = PMG
::RuleCache-
>new($ruledb);
128 $cfg->rewrite_config($rulecache, $param->{restart
});
133 __PACKAGE__-
>register_method ({
137 description
=> "Synchronize the LDAP database.",
139 additionalProperties
=> 0,
142 returns
=> { type
=> 'null'},
146 my $ldap_cfg = PVE
::INotify
::read_file
("pmg-ldap.conf");
147 PMG
::LDAPSet
::ldap_resync
($ldap_cfg, 1);
152 __PACKAGE__-
>register_method ({
156 description
=> "Generate /etc/pmg/pmg-api.pem (self signed certificate for GUI and REST API).",
158 additionalProperties
=> 0,
161 description
=> "Overwrite existing certificate.",
168 returns
=> { type
=> 'null'},
172 PMG
::Ticket
::generate_api_cert
($param->{force
});
177 __PACKAGE__-
>register_method ({
181 description
=> "Generate /etc/pmg/pmg-tls.pem (self signed certificate for encrypted SMTP traffic).",
183 additionalProperties
=> 0,
186 description
=> "Overwrite existing certificate.",
193 returns
=> { type
=> 'null'},
197 PMG
::Utils
::gen_proxmox_tls_cert
($param->{force
});
202 __PACKAGE__-
>register_method ({
206 description
=> "Generate required files in /etc/pmg/",
208 additionalProperties
=> 0,
211 returns
=> { type
=> 'null'},
215 my $cfg = PMG
::Config-
>new();
217 PMG
::Ticket
::generate_api_cert
();
218 PMG
::Ticket
::generate_csrf_key
();
219 PMG
::Ticket
::generate_auth_key
();
221 if ($cfg->get('mail', 'tls')) {
222 PMG
::Utils
::gen_proxmox_tls_cert
();
228 __PACKAGE__-
>register_method({
229 name
=> 'acme_register',
230 path
=> 'acme_register',
232 description
=> "Register a new ACME account with a compatible CA.",
234 additionalProperties
=> 0,
236 name
=> get_standard_option
('pmg-acme-account-name'),
237 contact
=> get_standard_option
('pmg-acme-account-contact'),
238 directory
=> get_standard_option
('pmg-acme-directory-url', {
243 returns
=> { type
=> 'null' },
247 my $custom_directory = 1;
248 if (!$param->{directory
}) {
249 my $directories = PMG
::API2
::ACME-
>get_directories({});
250 print "Directory endpoints:\n";
252 while ($i < @$directories) {
253 print $i, ") ", $directories->[$i]->{name
}, " (", $directories->[$i]->{url
}, ")\n";
256 print $i, ") Custom\n";
258 my $term = Term
::ReadLine-
>new('pmgconfig');
259 my $get_dir_selection = sub {
260 my $selection = $term->readline("Enter selection: ");
261 if ($selection =~ /^(\d+)$/) {
263 if ($selection == $i) {
264 $param->{directory
} = $term->readline("Enter custom URL: ");
266 } elsif ($selection < $i && $selection >= 0) {
267 $param->{directory
} = $directories->[$selection]->{url
};
268 $custom_directory = 0;
272 print "Invalid selection.\n";
276 while (!$param->{directory
}) {
277 die "Aborting.\n" if $attempts > 3;
278 $get_dir_selection->();
283 print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
284 my $meta = PMG
::API2
::ACME-
>get_meta({ directory
=> $param->{directory
} });
285 if ($meta->{termsOfService
}) {
286 my $tos = $meta->{termsOfService
};
287 print "Terms of Service: $tos\n";
288 my $term = Term
::ReadLine-
>new('pmgconfig');
289 my $agreed = $term->readline('Do you agree to the above terms? [y|N]: ');
290 die "Cannot continue without agreeing to ToS, aborting.\n"
291 if ($agreed !~ /^y$/i);
293 $param->{tos_url
} = $tos;
295 print "No Terms of Service found, proceeding.\n";
298 my $eab_enabled = $meta->{externalAccountRequired
};
299 if (!$eab_enabled && $custom_directory) {
300 my $term = Term
::ReadLine-
>new('pmgconfig');
301 my $agreed = $term->readline('Do you want to use external account binding? [y|N]: ');
302 $eab_enabled = ($agreed =~ /^y$/i);
303 } elsif ($eab_enabled) {
304 print "The CA requires external account binding.\n";
307 print "You should have received a key id and a key from your CA.\n";
308 my $term = Term
::ReadLine-
>new('pmgconfig');
309 my $eab_kid = $term->readline('Enter EAB key id: ');
310 my $eab_hmac_key = $term->readline('Enter EAB key: ');
312 $param->{'eab-kid'} = $eab_kid;
313 $param->{'eab-hmac-key'} = $eab_hmac_key;
316 print "\nAttempting to register account with '$param->{directory}'..\n";
318 $upid_exit->(PMG
::API2
::ACME-
>register_account($param));
321 my $print_cert_info = sub {
322 my ($schema, $cert, $options) = @_;
324 my $order = [qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san)];
325 PVE
::CLIFormatter
::print_api_result
(
326 $cert, $schema, $order, { %$options, noheader
=> 1, sort_key
=> 0 });
330 'dump' => [ __PACKAGE__
, 'dump', []],
331 sync
=> [ __PACKAGE__
, 'sync', []],
332 ldapsync
=> [ __PACKAGE__
, 'ldapsync', []],
333 apicert
=> [ __PACKAGE__
, 'apicert', []],
334 tlscert
=> [ __PACKAGE__
, 'tlscert', []],
335 init
=> [ __PACKAGE__
, 'init', []],
336 dkim_set
=> [ 'PMG::API2::DKIMSign', 'set_selector', []],
337 dkim_record
=> [ 'PMG::API2::DKIMSign', 'get_selector_info', [], undef,
340 die "no dkim_selector configured\n" if !defined($res->{record
});
341 print "$res->{record}\n";
345 info
=> [ 'PMG::API2::Certificates', 'info', [], { node
=> $nodename }, sub {
346 my ($res, $schema, $options) = @_;
348 if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
349 for my $cert (sort { $a->{filename
} cmp $b->{filename
} } @$res) {
350 $print_cert_info->($schema->{items
}, $cert, $options);
353 PVE
::CLIFormatter
::print_api_result
($res, $schema, undef, $options);
356 }, $PVE::RESTHandler
::standard_output_options
],
357 set
=> [ 'PMG::API2::Certificates', 'upload_custom_cert', ['type', 'certificates', 'key'], { node
=> $nodename }, sub {
358 my ($res, $schema, $options) = @_;
359 $print_cert_info->($schema, $res, $options);
360 }, $PVE::RESTHandler
::standard_output_options
],
361 delete => [ 'PMG::API2::Certificates', 'remove_custom_cert', ['type', 'restart'], { node
=> $nodename } ],
366 list
=> [ 'PMG::API2::ACME', 'account_index', [], {}, sub {
368 for my $acc (@$res) {
369 print "$acc->{name}\n";
372 register
=> [ __PACKAGE__
, 'acme_register', ['name', 'contact'], {}, $upid_exit ],
373 deactivate
=> [ 'PMG::API2::ACME', 'deactivate_account', ['name'], {}, $upid_exit ],
374 info
=> [ 'PMG::API2::ACME', 'get_account', ['name'], {}, sub {
375 my ($data, $schema, $options) = @_;
376 PVE
::CLIFormatter
::print_api_result
($data, $schema, undef, $options);
377 }, $PVE::RESTHandler
::standard_output_options
],
378 update
=> [ 'PMG::API2::ACME', 'update_account', ['name'], {}, $upid_exit ],
381 order
=> [ 'PMG::API2::Certificates', 'new_acme_cert', ['type'], { node
=> $nodename }, $upid_exit ],
384 renew
=> [ 'PMG::API2::Certificates', 'renew_acme_cert', ['type'], { node
=> $nodename }, $upid_exit ],
385 revoke
=> [ 'PMG::API2::Certificates', 'revoke_acme_cert', ['type'], { node
=> $nodename }, $upid_exit ],
388 list
=> [ 'PMG::API2::ACMEPlugin', 'index', [], {}, sub {
389 my ($data, $schema, $options) = @_;
390 PVE
::CLIFormatter
::print_api_result
($data, $schema, undef, $options);
391 }, $PVE::RESTHandler
::standard_output_options
],
392 config
=> [ 'PMG::API2::ACMEPlugin', 'get_plugin_config', ['id'], {}, sub {
393 my ($data, $schema, $options) = @_;
394 PVE
::CLIFormatter
::print_api_result
($data, $schema, undef, $options);
395 }, $PVE::RESTHandler
::standard_output_options
],
396 add
=> [ 'PMG::API2::ACMEPlugin', 'add_plugin', ['type', 'id'] ],
397 set
=> [ 'PMG::API2::ACMEPlugin', 'update_plugin', ['id'] ],
398 remove
=> [ 'PMG::API2::ACMEPlugin', 'delete_plugin', ['id'] ],