From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [PATCH v3 api 8/8] add acme and cert subcommands to pmgconfig
Date: Tue, 16 Mar 2021 11:24:15 +0100 [thread overview]
Message-ID: <20210316102424.25885-9-w.bumiller@proxmox.com> (raw)
In-Reply-To: <20210316102424.25885-1-w.bumiller@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
No changes since v2
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
next prev parent reply other threads:[~2021-03-16 10:24 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-03-16 10:24 [pmg-devel] [PATCH v3 api/gui/wtk/acme 0/many] Certificates & ACME Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 1/8] depend on libpmg-rs-perl and proxmox-acme Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 2/8] add PMG::CertHelpers module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 3/8] add PMG::NodeConfig module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 4/8] cluster: sync acme/ and acme-plugins.conf Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 5/8] api: add ACME and ACMEPlugin module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 6/8] add certificates api endpoint Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 7/8] add node-config api entry points Wolfgang Bumiller
2021-03-16 10:24 ` Wolfgang Bumiller [this message]
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 gui] add certificates and acme view Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 1/7] Utils: add ACME related utilities Wolfgang Bumiller
2021-03-16 12:18 ` [pmg-devel] applied-series[wtk]: " Thomas Lamprecht
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 2/7] add ACME related data models Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 3/7] add ACME forms Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 4/7] add certificate panel Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 5/7] add ACME account panel Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 6/7] add ACME plugin editing Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 7/7] add ACME domain editing Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 common] get_options: don't set optional positional params to `undef` Wolfgang Bumiller
2021-03-16 12:17 ` [pmg-devel] applied: " Thomas Lamprecht
2021-03-16 17:04 ` [pmg-devel] applied-series: [PATCH v3 api/gui/wtk/acme 0/many] Certificates & ACME Thomas Lamprecht
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=20210316102424.25885-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