[pmg-devel] [PATCH v2 api 8/8] add acme and cert subcommands to pmgconfig
Wolfgang Bumiller
w.bumiller at proxmox.com
Fri Mar 12 16:24:05 CET 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
src/PMG/CLI/pmgconfig.pm | 178 +++++++++++++++++++++++++++++++++++++++
1 file changed, 178 insertions(+)
diff --git a/src/PMG/CLI/pmgconfig.pm b/src/PMG/CLI/pmgconfig.pm
index 85edfa5..4f948cf 100644
--- a/src/PMG/CLI/pmgconfig.pm
+++ b/src/PMG/CLI/pmgconfig.pm
@@ -5,10 +5,13 @@ use warnings;
use IO::File;
use Data::Dumper;
+use Term::ReadLine;
+
use PVE::SafeSyslog;
use PVE::Tools qw(extract_param);
use PVE::INotify;
use PVE::CLIHandler;
+use PVE::JSONSchema qw(get_standard_option);
use PMG::RESTEnvironment;
use PMG::RuleDB;
@@ -18,14 +21,52 @@ use PMG::LDAPConfig;
use PMG::LDAPSet;
use PMG::Config;
use PMG::Ticket;
+
+use PMG::API2::ACME;
+use PMG::API2::ACMEPlugin;
+use PMG::API2::Certificates;
use PMG::API2::DKIMSign;
use base qw(PVE::CLIHandler);
+my $nodename = PVE::INotify::nodename();
+
sub setup_environment {
PMG::RESTEnvironment->setup_default_cli_env();
}
+my $upid_exit = sub {
+ my $upid = shift;
+ my $status = PVE::Tools::upid_read_status($upid);
+ print "Task $status\n";
+ exit($status eq 'OK' ? 0 : -1);
+};
+
+sub param_mapping {
+ my ($name) = @_;
+
+ my $load_file_and_encode = sub {
+ my ($filename) = @_;
+
+ return PVE::ACME::Challenge->encode_value('string', 'data', PVE::Tools::file_get_contents($filename));
+ };
+
+ my $mapping = {
+ 'upload_custom_cert' => [
+ 'certificates',
+ 'key',
+ ],
+ 'add_plugin' => [
+ ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
+ ],
+ 'update_plugin' => [
+ ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
+ ],
+ };
+
+ return $mapping->{$name};
+}
+
__PACKAGE__->register_method ({
name => 'dump',
path => 'dump',
@@ -184,6 +225,84 @@ __PACKAGE__->register_method ({
return undef;
}});
+__PACKAGE__->register_method({
+ name => 'acme_register',
+ path => 'acme_register',
+ method => 'POST',
+ description => "Register a new ACME account with a compatible CA.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ name => get_standard_option('pmg-acme-account-name'),
+ contact => get_standard_option('pmg-acme-account-contact'),
+ directory => get_standard_option('pmg-acme-directory-url', {
+ optional => 1,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ if (!$param->{directory}) {
+ my $directories = PMG::API2::ACME->get_directories({});
+ print "Directory endpoints:\n";
+ my $i = 0;
+ while ($i < @$directories) {
+ print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, ")\n";
+ $i++;
+ }
+ print $i, ") Custom\n";
+
+ my $term = Term::ReadLine->new('pmgconfig');
+ my $get_dir_selection = sub {
+ my $selection = $term->readline("Enter selection: ");
+ if ($selection =~ /^(\d+)$/) {
+ $selection = $1;
+ if ($selection == $i) {
+ $param->{directory} = $term->readline("Enter custom URL: ");
+ return;
+ } elsif ($selection < $i && $selection >= 0) {
+ $param->{directory} = $directories->[$selection]->{url};
+ return;
+ }
+ }
+ print "Invalid selection.\n";
+ };
+
+ my $attempts = 0;
+ while (!$param->{directory}) {
+ die "Aborting.\n" if $attempts > 3;
+ $get_dir_selection->();
+ $attempts++;
+ }
+ }
+ print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
+ my $tos = PMG::API2::ACME->get_tos({ directory => $param->{directory} });
+ if ($tos) {
+ print "Terms of Service: $tos\n";
+ my $term = Term::ReadLine->new('pvenode');
+ my $agreed = $term->readline('Do you agree to the above terms? [y|N]: ');
+ die "Cannot continue without agreeing to ToS, aborting.\n"
+ if ($agreed !~ /^y$/i);
+
+ $param->{tos_url} = $tos;
+ } else {
+ print "No Terms of Service found, proceeding.\n";
+ }
+ print "\nAttempting to register account with '$param->{directory}'..\n";
+
+ $upid_exit->(PMG::API2::ACME->register_account($param));
+ }});
+
+my $print_cert_info = sub {
+ my ($schema, $cert, $options) = @_;
+
+ my $order = [qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san)];
+ PVE::CLIFormatter::print_api_result(
+ $cert, $schema, $order, { %$options, noheader => 1, sort_key => 0 });
+};
+
our $cmddef = {
'dump' => [ __PACKAGE__, 'dump', []],
sync => [ __PACKAGE__, 'sync', []],
@@ -198,6 +317,65 @@ our $cmddef = {
die "no dkim_selector configured\n" if !defined($res->{record});
print "$res->{record}\n";
}],
+
+ cert => {
+ info => [ 'PMG::API2::Certificates', 'info', [], { node => $nodename }, sub {
+ my ($res, $schema, $options) = @_;
+
+ if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
+ for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) {
+ $print_cert_info->($schema->{items}, $cert, $options);
+ }
+ } else {
+ PVE::CLIFormatter::print_api_result($res, $schema, undef, $options);
+ }
+
+ }, $PVE::RESTHandler::standard_output_options],
+ set => [ 'PMG::API2::Certificates', 'upload_custom_cert', ['type', 'certificates', 'key'], { node => $nodename }, sub {
+ my ($res, $schema, $options) = @_;
+ $print_cert_info->($schema, $res, $options);
+ }, $PVE::RESTHandler::standard_output_options],
+ delete => [ 'PMG::API2::Certificates', 'remove_custom_cert', ['type', 'restart'], { node => $nodename } ],
+ },
+
+ acme => {
+ account => {
+ list => [ 'PMG::API2::ACME', 'account_index', [], {}, sub {
+ my ($res) = @_;
+ for my $acc (@$res) {
+ print "$acc->{name}\n";
+ }
+ }],
+ register => [ __PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit ],
+ deactivate => [ 'PMG::API2::ACME', 'deactivate_account', ['name'], {}, $upid_exit ],
+ info => [ 'PMG::API2::ACME', 'get_account', ['name'], {}, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+ }, $PVE::RESTHandler::standard_output_options],
+ update => [ 'PMG::API2::ACME', 'update_account', ['name'], {}, $upid_exit ],
+ },
+ cert => {
+ order => [ 'PMG::API2::Certificates', 'new_acme_cert', ['type'], { node => $nodename }, $upid_exit ],
+
+
+ renew => [ 'PMG::API2::Certificates', 'renew_acme_cert', ['type'], { node => $nodename }, $upid_exit ],
+ revoke => [ 'PMG::API2::Certificates', 'revoke_acme_cert', ['type'], { node => $nodename }, $upid_exit ],
+ },
+ plugin => {
+ list => [ 'PMG::API2::ACMEPlugin', 'index', [], {}, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+ }, $PVE::RESTHandler::standard_output_options ],
+ config => [ 'PMG::API2::ACMEPlugin', 'get_plugin_config', ['id'], {}, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+ }, $PVE::RESTHandler::standard_output_options ],
+ add => [ 'PMG::API2::ACMEPlugin', 'add_plugin', ['type', 'id'] ],
+ set => [ 'PMG::API2::ACMEPlugin', 'update_plugin', ['id'] ],
+ remove => [ 'PMG::API2::ACMEPlugin', 'delete_plugin', ['id'] ],
+ },
+
+ },
};
--
2.20.1
More information about the pmg-devel
mailing list