From: Stoiko Ivanov <s.ivanov@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [RFC pmg-api 06/12] add helper module for handling PBS Integration
Date: Mon, 19 Oct 2020 21:02:03 +0200 [thread overview]
Message-ID: <20201019190209.11495-7-s.ivanov@proxmox.com> (raw)
In-Reply-To: <20201019190209.11495-1-s.ivanov@proxmox.com>
PBSTools.pm contains methods which eventually should be shared between
PVE and PMG, for:
* handling (sensitive) config-information (passwords, encryption keys)
* running PBS operations
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/Makefile | 1 +
src/PMG/PBSTools.pm | 239 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 240 insertions(+)
create mode 100644 src/PMG/PBSTools.pm
diff --git a/src/Makefile b/src/Makefile
index 05d9598..7f9726b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -66,6 +66,7 @@ LIBSOURCES = \
PMG/SMTP.pm \
PMG/Unpack.pm \
PMG/Backup.pm \
+ PMG/PBSTools.pm \
PMG/RuleCache.pm \
PMG/Statistic.pm \
PMG/UserConfig.pm \
diff --git a/src/PMG/PBSTools.pm b/src/PMG/PBSTools.pm
new file mode 100644
index 0000000..e2a2b60
--- /dev/null
+++ b/src/PMG/PBSTools.pm
@@ -0,0 +1,239 @@
+package PMG::PBSTools;
+
+# utility functions to talk with Proxmox Backup Server
+
+use strict;
+use warnings;
+# FIXME: cleanup to only needed modules:
+use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
+use HTTP::Request;
+use IO::File;
+use JSON;
+use LWP::UserAgent;
+use POSIX qw(strftime ENOENT);
+
+use PVE::Tools qw(run_command file_set_contents file_get_contents file_read_firstline trim dir_glob_foreach);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Systemd;
+
+# Helpers
+my $secret_dir;
+
+sub set_secret_dir {
+ my ($dir) = @_;
+ $secret_dir = $dir;
+}
+
+sub pbs_password_file_name {
+ my ($scfg, $storeid) = @_;
+
+ return "${secret_dir}/${storeid}.pw";
+}
+
+sub pbs_set_password {
+ my ($scfg, $storeid, $password) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($pwfile, "$password\n", 0600);
+}
+
+sub pbs_delete_password {
+ my ($scfg, $storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+
+ unlink $pwfile;
+}
+
+sub pbs_get_password {
+ my ($scfg, $storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+
+ return PVE::Tools::file_read_firstline($pwfile);
+}
+
+sub pbs_encryption_key_file_name {
+ my ($scfg, $storeid) = @_;
+
+ return "${secret_dir}/${storeid}.enc";
+}
+
+sub pbs_set_encryption_key {
+ my ($scfg, $storeid, $key) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($encfile, "$key\n", 0600);
+}
+
+sub pbs_delete_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+
+ if (!unlink $encfile) {
+ return if $! == ENOENT;
+ die "failed to delete encryption key! $!\n";
+ }
+}
+
+sub pbs_get_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+
+ return PVE::Tools::file_get_contents($encfile);
+}
+
+# Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
+sub pbs_open_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encryption_key_file = pbs_encryption_key_file_name($scfg, $storeid);
+
+ my $keyfd;
+ if (!open($keyfd, '<', $encryption_key_file)) {
+ return undef if $! == ENOENT;
+ die "failed to open encryption key: $encryption_key_file: $!\n";
+ }
+
+ return $keyfd;
+}
+
+my $USE_CRYPT_PARAMS = {
+ backup => 1,
+ restore => 1,
+ 'upload-log' => 1,
+};
+
+my sub do_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+
+ #FIXME: my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
+ my $use_crypto = 0;
+
+ my $client_exe = '/usr/bin/proxmox-backup-client';
+ die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
+ if ! -x $client_exe;
+
+ my $server = $scfg->{server};
+ my $datastore = $scfg->{datastore};
+ my $username = $scfg->{username} // 'root@pam';
+
+ my $userns_cmd = delete $opts{userns_cmd};
+
+ my $cmd = [];
+
+ push @$cmd, @$userns_cmd if defined($userns_cmd);
+
+ push @$cmd, $client_exe, $client_cmd;
+
+ # This must live in the top scope to not get closed before the `run_command`
+ my $keyfd;
+ if ($use_crypto) {
+ if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) {
+ my $flags = fcntl($keyfd, F_GETFD, 0)
+ // die "failed to get file descriptor flags: $!\n";
+ fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
+ or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
+ push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
+ } else {
+ push @$cmd, '--crypt-mode=none';
+ }
+ }
+
+ push @$cmd, @$param if defined($param);
+
+ push @$cmd, "--repository", "$username\@$server:$datastore";
+
+ local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
+
+ local $ENV{PBS_FINGERPRINT} = $scfg->{fingerprint};
+
+ # no ascii-art on task logs
+ local $ENV{PROXMOX_OUTPUT_NO_BORDER} = 1;
+ local $ENV{PROXMOX_OUTPUT_NO_HEADER} = 1;
+
+ if (my $logfunc = $opts{logfunc}) {
+ $logfunc->("run: " . join(' ', @$cmd));
+ }
+
+ run_command($cmd, %opts);
+}
+
+# FIXME: External perl code should NOT have access to this.
+#
+# There should be separate functions to
+# - make backups
+# - restore backups
+# - restore files
+# with a sane API
+sub run_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+ return do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, %opts);
+}
+
+sub run_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_;
+
+ my $json_str = '';
+ my $outfunc = sub { $json_str .= "$_[0]\n" };
+
+ $param = [] if !defined($param);
+ $param = [ $param ] if !ref($param);
+
+ $param = [@$param, '--output-format=json'] if !$no_output;
+
+ do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
+ outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
+
+ return undef if $no_output;
+
+ my $res = decode_json($json_str);
+
+ return $res;
+}
+
+my $autogen_encryption_key = sub {
+ my ($scfg, $storeid) = @_;
+ my $encfile = pbs_encryption_key_file_name($storeid);
+ run_command(['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile]);
+};
+
+sub print_snapshot {
+ my ($storeid, $btype, $bid, $btime) = @_;
+
+ my $time_str = strftime("%FT%TZ", gmtime($btime));
+ my $volname = "backup/${btype}/${bid}/${time_str}";
+
+ return "${storeid}:${volname}";
+}
+
+sub status {
+ my ($class, $storeid, $scfg) = @_;
+
+ my $total = 0;
+ my $free = 0;
+ my $used = 0;
+ my $active = 0;
+
+ eval {
+ my $res = run_client_cmd($scfg, $storeid, "status");
+
+ $active = 1;
+ $total = $res->{total};
+ $used = $res->{used};
+ $free = $res->{avail};
+ };
+ if (my $err = $@) {
+ warn $err;
+ }
+
+ return ($total, $free, $used, $active);
+}
+
+1;
--
2.20.1
next prev parent reply other threads:[~2020-10-19 19:03 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-10-19 19:01 [pmg-devel] [RFC pmg-api 00/12] POC PBS integration Stoiko Ivanov
2020-10-19 19:01 ` [pmg-devel] [RFC pmg-api 01/12] drop left-over commented out code Stoiko Ivanov
2020-10-19 19:01 ` [pmg-devel] [RFC pmg-api 02/12] Backup: split backup creation and creating tar Stoiko Ivanov
2020-10-20 5:43 ` Dietmar Maurer
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 03/12] Restore: optionally restore from directory Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 04/12] Backup: push restore options to PMG::Backup Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 05/12] debian: add dependency on proxmox-backup-client Stoiko Ivanov
2020-10-19 19:02 ` Stoiko Ivanov [this message]
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 07/12] PBSTools: add methods for managing backups Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 08/12] PBSTools: add systemd-timer helpers Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 09/12] add initial SectionConfig for pbs Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 10/12] Add API2 module for PBS configuration Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 11/12] Add API2 module for per-node backups to PBS Stoiko Ivanov
2020-10-19 19:02 ` [pmg-devel] [RFC pmg-api 12/12] pbs-integration: add CLI calls to pmgbackup Stoiko Ivanov
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=20201019190209.11495-7-s.ivanov@proxmox.com \
--to=s.ivanov@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.