[pmg-devel] [PATCH v2 api 8/8] add acme and cert subcommands to pmgconfig

Stoiko Ivanov s.ivanov at proxmox.com
Mon Mar 15 22:39:44 CET 2021


On Fri, 12 Mar 2021 16:23:57 +0100
Wolfgang Bumiller <w.bumiller at proxmox.com> wrote:

> 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 } ],
small nit: by adding 'restart' as fixed parameter here I get an
uninitialized warning in JSONSchema - maybe just drop it?

> +    },
> +
> +    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'] ],
> +	},
> +
> +    },
>  };
>  
>  





More information about the pmg-devel mailing list