]> git.proxmox.com Git - pve-manager-legacy.git/commitdiff
acme plugins: improve API
authorFabian Grünbichler <f.gruenbichler@proxmox.com>
Mon, 20 Apr 2020 21:08:41 +0000 (23:08 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Sun, 3 May 2020 12:10:17 +0000 (14:10 +0200)
add checks, encoding of loaded data files, update API path, proper inclusion into API tree

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
PVE/API2/ACMEAccount.pm
PVE/API2/ACMEPlugin.pm
PVE/API2/Cluster.pm
PVE/CLI/pvenode.pm

index 29d5dcf3a924349460afe442484bee551e1b62de..28f39b3b6dc3b9819c69e660cfe4bcd9e1bd1119 100644 (file)
@@ -10,6 +10,13 @@ use PVE::JSONSchema qw(get_standard_option);
 use PVE::RPCEnvironment;
 use PVE::Tools qw(extract_param);
 
+use PVE::API2::ACMEPlugin;
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::ACMEPlugin",
+    path => 'plugins',
+});
+
 use base qw(PVE::RESTHandler);
 
 my $acme_directories = [
@@ -58,6 +65,7 @@ __PACKAGE__->register_method ({
            { name => 'account' },
            { name => 'tos' },
            { name => 'directories' },
+           { name => 'plugins' },
        ];
     }});
 
index 0a2a0b769a850cad3bfa6c8433a8b569163cfb78..4c87242fa3d3c5dd2665be171071fc825df2e123 100644 (file)
@@ -6,9 +6,12 @@ use warnings;
 use PVE::ACME::Challenge;
 use PVE::ACME::DNSChallenge;
 use PVE::ACME::StandAlone;
+use PVE::JSONSchema qw(register_standard_option get_standard_option);
 use PVE::Tools qw(extract_param);
 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file);
+
 use MIME::Base64;
+use Storable qw(dclone);
 
 use base qw(PVE::RESTHandler);
 
@@ -22,50 +25,151 @@ PVE::ACME::DNSChallenge->register();
 PVE::ACME::StandAlone->register();
 PVE::ACME::Challenge->init();
 
+PVE::JSONSchema::register_standard_option('pve-acme-pluginid', {
+    type => 'string',
+    format => 'pve-configid',
+    description => 'Unique identifier for ACME plugin instance.',
+});
+
+my $plugin_type_enum = PVE::ACME::Challenge->lookup_types();
+
+my $modify_cfg_for_api = sub {
+    my ($cfg, $pluginid) = @_;
+
+    die "ACME plugin '$pluginid' not defined\n"
+       if !defined($cfg->{ids}->{$pluginid});
+
+    my $plugin_cfg = dclone($cfg->{ids}->{$pluginid});
+    $plugin_cfg->{plugin} = $pluginid;
+    $plugin_cfg->{digest} = $cfg->{digest};
+
+    return $plugin_cfg;
+};
+
+__PACKAGE__->register_method ({
+    name => 'index',
+    path => '',
+    method => 'GET',
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Modify' ]],
+    },
+    description => "ACME plugin index.",
+    protected => 1,
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           type => {
+               description => "Only list storage of specific type",
+               type => 'string',
+               enum => $plugin_type_enum,
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {
+               plugin => get_standard_option('pve-acme-pluginid'),
+           },
+       },
+       links => [ { rel => 'child', href => "{plugin}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = load_config();
+
+       my $res =[];
+
+       foreach my $pluginid (keys %{$cfg->{ids}}) {
+           my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid);
+           next if $param->{type} && $param->{type} ne $plugin_cfg->{type};
+           push @$res, $plugin_cfg;
+       }
+
+       return $res;
+
+    }});
+
 __PACKAGE__->register_method({
     name => 'get_plugin_config',
-    path => 'plugin',
+    path => '{id}',
     method => 'GET',
-    description => "Get ACME DNS plugin configurations.",
+    description => "Get ACME DNS plugin configuration.",
     permissions => {
-       check => ['perm', '/', [ 'Sys.Modily' ]],
+       check => ['perm', '/', [ 'Sys.Modify' ]],
     },
     protected => 1,
     parameters => {
        additionalProperties => 0,
        properties => {
+           id => get_standard_option('pve-acme-pluginid'),
        },
     },
     returns => {
        type => 'object',
     },
     code => sub {
+       my ($param) = @_;
 
-       return  load_config();
+       return $modify_cfg_for_api->(load_config(), $param->{id});
     }});
 
 my $update_config = sub {
     my ($id, $op, $type, $param) = @_;
 
-    my $conf = load_config();
+    my $cfg = load_config();
 
     if ( $op eq "add" ) {
        die "Section with ID: $id already exists\n"
-           if defined($conf->{ids}->{$id});
-
-       $conf->{ids}->{$id} = $param;
-       $conf->{ids}->{$id}->{type} = $type;
+           if defined($cfg->{ids}->{$id});
+
+       my $plugin = PVE::ACME::Challenge->lookup($type);
+       my $opts = $plugin->check_config($id, $param, 1, 1);
+
+       $cfg->{ids}->{$id} = $opts;
+       $cfg->{ids}->{$id}->{type} = $type;
+    } elsif ($op eq "update") {
+       die "Section with ID; $id does not exist\n"
+           if !defined($cfg->{ids}->{$id});
+
+       my $delete = extract_param($param, 'delete');
+
+       $type = $cfg->{ids}->{$id}->{type};
+       my $plugin = PVE::ACME::Challenge->lookup($type);
+       my $opts = $plugin->check_config($id, $param, 0, 1);
+       if ($delete) {
+           my $options = $plugin->private()->{options}->{$type};
+           foreach my $k (PVE::Tools::split_list($delete)) {
+               my $d = $options->{$k} || die "no such option '$k'\n";
+               die "unable to delete required option '$k'\n" if !$d->{optional};
+               die "unable to delete fixed option '$k'\n" if $d->{fixed};
+               die "cannot set and delete property '$k' at the same time!\n"
+                   if defined($opts->{$k});
+
+               delete $cfg->{ids}->{$id}->{$k};
+           }
+       }
+
+       for my $k (keys %$opts) {
+           print "$k: $opts->{$k}\n";
+           $cfg->{ids}->{$id}->{$k} = $opts->{$k};
+       }
     } elsif ($op eq "del") {
-       delete $conf->{ids}->{$id};
+       delete $cfg->{ids}->{$id};
+    } else {
+       die 'undefined config update operation\n' if !defined($op);
+       die "unknown config update operation '$op'\n";
     }
 
-
-    PVE::Cluster::cfs_write_file($FILENAME, $conf);
+    PVE::Cluster::cfs_write_file($FILENAME, $cfg);
 };
 
 __PACKAGE__->register_method({
     name => 'add_plugin',
-    path => 'plugin',
+    path => '',
     method => 'POST',
     description => "Add ACME DNS plugin configuration.",
     permissions => {
@@ -86,9 +190,31 @@ __PACKAGE__->register_method({
        return undef;
     }});
 
+__PACKAGE__->register_method({
+    name => 'update_plugin',
+    path => '{id}',
+    method => 'PUT',
+    description => "Update ACME DNS plugin configuration.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Modify' ]],
+    },
+    protected => 1,
+    parameters => PVE::ACME::Challenge->updateSchema(),
+    returns => { type => "null" },
+    code => sub {
+       my ($param) = @_;
+
+       my $id = extract_param($param, 'id');
+
+       PVE::Cluster::cfs_lock_file($FILENAME, undef, $update_config, $id, "update", undef, $param);
+       die "$@" if $@;
+
+       return undef;
+    }});
+
 __PACKAGE__->register_method({
     name => 'delete_plugin',
-    path => 'plugin',
+    path => '{id}',
     method => 'DELETE',
     description => "Delete ACME DNS plugin configuration.",
     permissions => {
@@ -96,13 +222,10 @@ __PACKAGE__->register_method({
     },
     protected => 1,
     parameters => {
-               additionalProperties => 0,
-               properties => {
-                   id => {
-                       description => "Plugin configuration name",
-                       type => 'string',
-                   },
-               },
+       additionalProperties => 0,
+       properties => {
+           id => get_standard_option('pve-acme-pluginid'),
+       },
     },
     returns => { type => "null" },
     code => sub {
index 0810da0a4477e039ed197b72876efb99b80b34b9..76560fa46a1a7b0200eeb46ad7f02eb0e604c7e0 100644 (file)
@@ -67,11 +67,6 @@ __PACKAGE__->register_method ({
     path => 'acme',
 });
 
-__PACKAGE__->register_method ({
-    subclass => "PVE::API2::ACMEPlugin",
-    path => 'acmeplugin',
-});
-
 __PACKAGE__->register_method ({
     subclass => "PVE::API2::Cluster::Ceph",
     path => 'ceph',
index fd706a91e4683937d21f33bbd471d9c884d885d8..0efdcd56ed00605c62509dd7186a36cf013efbfe 100644 (file)
@@ -11,6 +11,7 @@ use PVE::API2::NodeConfig;
 use PVE::API2::Nodes;
 use PVE::API2::Tasks;
 
+use PVE::ACME::Challenge;
 use PVE::CertHelpers;
 use PVE::Certificate;
 use PVE::Exception qw(raise_param_exc raise);
@@ -41,13 +42,22 @@ my $upid_exit = sub {
 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',
+           ['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],
        ],
     };
 
@@ -212,24 +222,17 @@ our $cmddef = {
            revoke => [ 'PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit ],
        },
        plugin => {
-           get => [ 'PVE::API2::ACMEPlugin', 'get_plugin_config', [], {},
-                    sub {
-                        my $conf = shift;
-                        print "Name\tType\tStatus\tapi\tdata\n";
-                        foreach my $key (keys %{$conf->{ids}} ) {
-                            my $type = $conf->{ids}->{$key}->{type};
-                            my $status = $conf->{ids}->{$key}->{disable} ?
-                                "disabled" : "active";
-                            my $api = $conf->{ids}->{$key}->{api} ?
-                                $conf->{ids}->{$key}->{api} : "none";
-                            my $data = $conf->{ids}->{$key}->{data} ?
-                                $conf->{ids}->{$key}->{data} : "none";
-
-                            print "$key\t$type\t$status\t$api\t$data\n";
-                        }
-                    } ],
+           list => [ 'PVE::API2::ACMEPlugin', 'index', [], {}, sub {
+               my ($data, $schema, $options) = @_;
+               PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+           }, $PVE::RESTHandler::standard_output_options ],
+           config => [ 'PVE::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 => [ 'PVE::API2::ACMEPlugin', 'add_plugin', ['type', 'id'] ],
-           del => [ 'PVE::API2::ACMEPlugin', 'delete_plugin', ['id'] ],
+           set => [ 'PVE::API2::ACMEPlugin', 'update_plugin', ['id'] ],
+           remove => [ 'PVE::API2::ACMEPlugin', 'delete_plugin', ['id'] ],
        },
 
     },