public inbox for pmg-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [PATCH api 8/8] add acme and cert subcommands to pmgconfig
Date: Tue,  9 Mar 2021 15:13:52 +0100	[thread overview]
Message-ID: <20210309141401.19237-9-w.bumiller@proxmox.com> (raw)
In-Reply-To: <20210309141401.19237-1-w.bumiller@proxmox.com>

Signed-off-by: Wolfgang Bumiller <w.bumiller@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





  parent reply	other threads:[~2021-03-09 14:14 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-09 14:13 [pmg-devel] [RFC api/gui/wtk/acme 0/many] Certificates & ACME Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 1/8] depend on libpmg-rs-perl and proxmox-acme Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 2/8] add PMG::CertHelpers module Wolfgang Bumiller
2021-03-11 10:05   ` Dominik Csapak
2021-03-12 13:55     ` Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 3/8] add PMG::NodeConfig module Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 4/8] cluster: sync acme/ and acme-plugins.conf Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 5/8] api: add ACME and ACMEPlugin module Wolfgang Bumiller
2021-03-11 10:41   ` Dominik Csapak
2021-03-12 14:10     ` Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 6/8] add certificates api endpoint Wolfgang Bumiller
2021-03-11 11:06   ` Dominik Csapak
2021-03-12 14:51     ` Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH api 7/8] add node-config api entry points Wolfgang Bumiller
2021-03-09 14:13 ` Wolfgang Bumiller [this message]
2021-03-09 14:13 ` [pmg-devel] [PATCH gui] add certificates and acme view Wolfgang Bumiller
2021-03-11 12:35   ` Dominik Csapak
2021-03-09 14:13 ` [pmg-devel] [PATCH acme] add missing 'use PVE::Acme' statement Wolfgang Bumiller
2021-03-12 15:00   ` [pmg-devel] applied: " Thomas Lamprecht
2021-03-09 14:13 ` [pmg-devel] [PATCH widget-toolkit 1/7] Utils: add ACME related utilities Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH widget-toolkit 2/7] add ACME related data models Wolfgang Bumiller
2021-03-11 12:41   ` Dominik Csapak
2021-03-09 14:13 ` [pmg-devel] [PATCH widget-toolkit 3/7] add ACME forms: Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH widget-toolkit 4/7] add certificate panel Wolfgang Bumiller
2021-03-09 14:13 ` [pmg-devel] [PATCH widget-toolkit 5/7] add ACME account panel Wolfgang Bumiller
2021-03-11 13:51   ` Dominik Csapak
2021-03-11 15:14     ` Thomas Lamprecht
2021-03-11 15:16       ` Dominik Csapak
2021-03-11 15:27         ` Thomas Lamprecht
2021-03-09 14:14 ` [pmg-devel] [PATCH widget-toolkit 6/7] add ACME plugin editing Wolfgang Bumiller
2021-03-09 14:14 ` [pmg-devel] [PATCH widget-toolkit 7/7] add ACME domain editing Wolfgang Bumiller
2021-03-10 12:27 ` [pmg-devel] [RFC api/gui/wtk/acme 0/many] Certificates & ACME Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210309141401.19237-9-w.bumiller@proxmox.com \
    --to=w.bumiller@proxmox.com \
    --cc=pmg-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal