diff --git a/PVE/API2/Certificates.pm b/PVE/API2/Certificates.pm new file mode 100644 index 00000000..fd75ba85 --- /dev/null +++ b/PVE/API2/Certificates.pm @@ -0,0 +1,207 @@ +package PVE::API2::Certificates; + +use strict; +use warnings; + +use PVE::API2::ACME; +use PVE::Certificate; +use PVE::CertHelpers;; +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::Tools qw(extract_param file_get_contents file_set_contents); + +use base qw(PVE::RESTHandler); + + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::ACME", + path => 'acme', +}); + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + permissions => { user => 'all' }, + description => "Node index.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{name}" } ], + }, + code => sub { + my ($param) = @_; + + return [ + { name => 'acme' }, + { name => 'custom' }, + { name => 'info' }, + ]; + }, +}); + +__PACKAGE__->register_method ({ + name => 'info', + path => 'info', + method => 'GET', + permissions => { user => 'all' }, + proxyto => 'node', + description => "Get information about node's certificates.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, + }, + returns => { + type => 'array', + items => get_standard_option('pve-certificate-info'), + }, + code => sub { + my ($param) = @_; + + my $node_path = "/etc/pve/nodes/$param->{node}"; + + my $res = []; + my $cert_paths = [ + '/etc/pve/pve-root-ca.pem', + "$node_path/pve-ssl.pem", + "$node_path/pveproxy-ssl.pem", + ]; + for my $path (@$cert_paths) { + eval { + my $info = PVE::Certificate::get_certificate_info($path); + push @$res, $info if $info; + }; + } + return $res; + }, +}); + +__PACKAGE__->register_method ({ + name => 'upload_custom_cert', + path => 'custom', + method => 'POST', + description => 'Upload or update custom certificate chain and key.', + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + certificates => { + type => 'string', + format => 'pem-certificate-chain', + description => 'PEM encoded certificate (chain).', + }, + key => { + type => 'string', + description => 'PEM encoded private key.', + format => 'pem-string', + optional => 1, + }, + force => { + type => 'boolean', + description => 'Overwrite existing custom or ACME certificate files.', + optional => 1, + default => 0, + }, + restart => { + type => 'boolean', + description => 'Restart pveproxy.', + optional => 1, + default => 0, + }, + }, + }, + returns => get_standard_option('pve-certificate-info'), + code => sub { + my ($param) = @_; + + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + + my $certs = extract_param($param, 'certificates'); + $certs = PVE::Certificate::strip_leading_text($certs); + + my $key = extract_param($param, 'key'); + if ($key) { + $key = PVE::Certificate::strip_leading_text($key); + } else { + raise_param_exc({'key' => "Attempted to upload custom certificate without (existing) key."}) + if ! -e "${cert_prefix}.key"; + } + + my $info; + + my $code = sub { + print "Setting custom certificate files\n"; + $info = PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force}); + + if ($param->{restart}) { + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + } + }; + + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; + + return $info; + }}); + +__PACKAGE__->register_method ({ + name => 'remove_custom_cert', + path => 'custom', + method => 'DELETE', + description => 'DELETE custom certificate chain and key.', + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + restart => { + type => 'boolean', + description => 'Restart pveproxy.', + optional => 1, + default => 0, + }, + }, + }, + returns => { + type => 'null', + }, + code => sub { + my ($param) = @_; + + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + + my $code = sub { + print "Deleting custom certificate files\n"; + unlink "${cert_prefix}.pem"; + unlink "${cert_prefix}.key"; + + if ($param->{restart}) { + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + } + }; + + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; + + return undef; + }}); + +1; diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile index 44b9cf7c..9862e498 100644 --- a/PVE/API2/Makefile +++ b/PVE/API2/Makefile @@ -14,6 +14,7 @@ PERLSOURCE = \ Pool.pm \ Tasks.pm \ Network.pm \ + Certificates.pm \ ACME.pm \ ACMEAccount.pm \ NodeConfig.pm \ diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm index 42b932cf..25f0e180 100644 --- a/PVE/API2/Nodes.pm +++ b/PVE/API2/Nodes.pm @@ -41,6 +41,7 @@ use PVE::API2::APT; use PVE::API2::Ceph; use PVE::API2::Firewall::Host; use PVE::API2::Replication; +use PVE::API2::Certificates; use PVE::API2::NodeConfig; use Digest::MD5; use Digest::SHA; @@ -119,6 +120,12 @@ __PACKAGE__->register_method ({ path => 'replication', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Certificates", + path => 'certificates', +}); + + __PACKAGE__->register_method ({ subclass => "PVE::API2::NodeConfig", path => 'config', @@ -177,6 +184,7 @@ __PACKAGE__->register_method ({ { name => 'stopall' }, { name => 'netstat' }, { name => 'firewall' }, + { name => 'certificates' }, { name => 'config' }, ];