From 0351f23ba46ad3e6336f07683ee10c1dd2852bc7 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 8 Jul 2020 13:52:17 +0200 Subject: [PATCH] client: introduce --keyfd parameter This is a more convenient way to pass along the key when creating encrypted backups of unprivileged containers in PVE where the unprivileged user namespace cannot access `/etc/pve/priv`. Signed-off-by: Wolfgang Bumiller --- src/backup/key_derivation.rs | 10 ++- src/bin/proxmox-backup-client.rs | 91 ++++++++++++++++++------ src/bin/proxmox_backup_client/catalog.rs | 3 +- src/bin/proxmox_backup_client/key.rs | 6 ++ 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index 7dd23e67..4befe525 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -158,10 +158,14 @@ fn do_load_and_decrypt_key( path: &std::path::Path, passphrase: &dyn Fn() -> Result, Error>, ) -> Result<([u8;32], DateTime), Error> { - let raw = file_get_contents(&path)?; - let data = String::from_utf8(raw)?; + decrypt_key(&file_get_contents(&path)?, passphrase) +} - let key_config: KeyConfig = serde_json::from_str(&data)?; +pub fn decrypt_key( + mut keydata: &[u8], + passphrase: &dyn Fn() -> Result, Error>, +) -> Result<([u8;32], DateTime), Error> { + let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; let raw_data = key_config.data; let created = key_config.created; diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index a6cbcc1e..7d4f28fa 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1,5 +1,7 @@ use std::collections::{HashSet, HashMap}; -use std::io::{self, Write, Seek, SeekFrom}; +use std::convert::TryFrom; +use std::io::{self, Read, Write, Seek, SeekFrom}; +use std::os::unix::io::{FromRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::pin::Pin; use std::sync::{Arc, Mutex}; @@ -27,7 +29,7 @@ use proxmox_backup::client::*; use proxmox_backup::pxar::catalog::*; use proxmox_backup::backup::{ archive_type, - load_and_decrypt_key, + decrypt_key, verify_chunk_size, ArchiveType, AsyncReadChunk, @@ -66,6 +68,11 @@ pub const KEYFILE_SCHEMA: Schema = StringSchema::new( "Path to encryption key. All data will be encrypted using this key.") .schema(); +pub const KEYFD_SCHEMA: Schema = IntegerSchema::new( + "Pass an encryption key via an already opened file descriptor.") + .minimum(0) + .schema(); + const CHUNK_SIZE_SCHEMA: Schema = IntegerSchema::new( "Chunk size in KB. Must be a power of 2.") .minimum(64) @@ -663,21 +670,48 @@ fn spawn_catalog_upload( Ok((catalog, catalog_result_rx)) } -fn keyfile_parameters(param: &Value) -> Result<(Option, CryptMode), Error> { +fn keyfile_parameters(param: &Value) -> Result<(Option>, CryptMode), Error> { let keyfile = match param.get("keyfile") { Some(Value::String(keyfile)) => Some(keyfile), Some(_) => bail!("bad --keyfile parameter type"), None => None, }; + let key_fd = match param.get("keyfd") { + Some(Value::Number(key_fd)) => Some( + RawFd::try_from(key_fd + .as_i64() + .ok_or_else(|| format_err!("bad key fd: {:?}", key_fd))? + ) + .map_err(|err| format_err!("bad key fd: {:?}: {}", key_fd, err))? + ), + Some(_) => bail!("bad --keyfd parameter type"), + None => None, + }; + let crypt_mode: Option = match param.get("crypt-mode") { Some(mode) => Some(serde_json::from_value(mode.clone())?), None => None, }; - Ok(match (keyfile, crypt_mode) { + let keydata = match (keyfile, key_fd) { + (None, None) => None, + (Some(_), Some(_)) => bail!("--keyfile and --keyfd are mutually exclusive"), + (Some(keyfile), None) => Some(file_get_contents(keyfile)?), + (None, Some(fd)) => { + let input = unsafe { std::fs::File::from_raw_fd(fd) }; + let mut data = Vec::new(); + let _len: usize = { input }.read_to_end(&mut data) + .map_err(|err| { + format_err!("error reading encryption key from fd {}: {}", fd, err) + })?; + Some(data) + } + }; + + Ok(match (keydata, crypt_mode) { // no parameters: - (None, None) => match key::find_default_encryption_key()? { + (None, None) => match key::read_optional_default_encryption_key()? { Some(key) => (Some(key), CryptMode::Encrypt), None => (None, CryptMode::None), }, @@ -686,21 +720,21 @@ fn keyfile_parameters(param: &Value) -> Result<(Option, CryptMode), Err (None, Some(CryptMode::None)) => (None, CryptMode::None), // just --crypt-mode other than none - (None, Some(crypt_mode)) => match key::find_default_encryption_key()? { + (None, Some(crypt_mode)) => match key::read_optional_default_encryption_key()? { None => bail!("--crypt-mode without --keyfile and no default key file available"), - Some(path) => (Some(path), crypt_mode), + Some(key) => (Some(key), crypt_mode), } // just --keyfile - (Some(keyfile), None) => (Some(PathBuf::from(keyfile)), CryptMode::Encrypt), + (Some(key), None) => (Some(key), CryptMode::Encrypt), // --keyfile and --crypt-mode=none (Some(_), Some(CryptMode::None)) => { - bail!("--keyfile and --crypt-mode=none are mutually exclusive"); + bail!("--keyfile/--keyfd and --crypt-mode=none are mutually exclusive"); } // --keyfile and --crypt-mode other than none - (Some(keyfile), Some(crypt_mode)) => (Some(PathBuf::from(keyfile)), crypt_mode), + (Some(key), Some(crypt_mode)) => (Some(key), crypt_mode), }) } @@ -730,6 +764,10 @@ fn keyfile_parameters(param: &Value) -> Result<(Option, CryptMode), Err schema: KEYFILE_SCHEMA, optional: true, }, + "keyfd": { + schema: KEYFD_SCHEMA, + optional: true, + }, "crypt-mode": { type: CryptMode, optional: true, @@ -803,7 +841,7 @@ async fn create_backup( verify_chunk_size(size)?; } - let (keyfile, crypt_mode) = keyfile_parameters(¶m)?; + let (keydata, crypt_mode) = keyfile_parameters(¶m)?; let backup_id = param["backup-id"].as_str().unwrap_or(&proxmox::tools::nodename()); @@ -902,10 +940,10 @@ async fn create_backup( println!("Starting protocol: {}", start_time.to_rfc3339_opts(chrono::SecondsFormat::Secs, false)); - let (crypt_config, rsa_encrypted_key) = match keyfile { + let (crypt_config, rsa_encrypted_key) = match keydata { None => (None, None), - Some(path) => { - let (key, created) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?; + Some(key) => { + let (key, created) = decrypt_key(&key, &key::get_encryption_key_password)?; let crypt_config = CryptConfig::new(key)?; @@ -1173,6 +1211,10 @@ We do not extraxt '.pxar' archives when writing to standard output. schema: KEYFILE_SCHEMA, optional: true, }, + "keyfd": { + schema: KEYFD_SCHEMA, + optional: true, + }, "crypt-mode": { type: CryptMode, optional: true, @@ -1207,12 +1249,12 @@ async fn restore(param: Value) -> Result { let target = tools::required_string_param(¶m, "target")?; let target = if target == "-" { None } else { Some(target) }; - let (keyfile, _crypt_mode) = keyfile_parameters(¶m)?; + let (keydata, _crypt_mode) = keyfile_parameters(¶m)?; - let crypt_config = match keyfile { + let crypt_config = match keydata { None => None, - Some(path) => { - let (key, _) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?; + Some(key) => { + let (key, _) = decrypt_key(&key, &key::get_encryption_key_password)?; Some(Arc::new(CryptConfig::new(key)?)) } }; @@ -1337,6 +1379,10 @@ async fn restore(param: Value) -> Result { schema: KEYFILE_SCHEMA, optional: true, }, + "keyfd": { + schema: KEYFD_SCHEMA, + optional: true, + }, "crypt-mode": { type: CryptMode, optional: true, @@ -1355,12 +1401,12 @@ async fn upload_log(param: Value) -> Result { let mut client = connect(repo.host(), repo.user())?; - let (keyfile, crypt_mode) = keyfile_parameters(¶m)?; + let (keydata, crypt_mode) = keyfile_parameters(¶m)?; - let crypt_config = match keyfile { + let crypt_config = match keydata { None => None, - Some(path) => { - let (key, _created) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?; + Some(key) => { + let (key, _created) = decrypt_key(&key, &key::get_encryption_key_password)?; let crypt_config = CryptConfig::new(key)?; Some(Arc::new(crypt_config)) } @@ -1763,7 +1809,6 @@ impl ReadAt for BufferedDynamicReadAt { buf: &'a mut [u8], offset: u64, ) -> MaybeReady, ReadAtOperation<'a>> { - use std::io::Read; MaybeReady::Ready(tokio::task::block_in_place(move || { let mut reader = self.inner.lock().unwrap(); reader.seek(SeekFrom::Start(offset))?; diff --git a/src/bin/proxmox_backup_client/catalog.rs b/src/bin/proxmox_backup_client/catalog.rs index 595b5bab..ecc1278b 100644 --- a/src/bin/proxmox_backup_client/catalog.rs +++ b/src/bin/proxmox_backup_client/catalog.rs @@ -16,7 +16,6 @@ use crate::{ REPO_URL_SCHEMA, extract_repository_from_value, record_repository, - load_and_decrypt_key, api_datastore_latest_snapshot, complete_repository, complete_backup_snapshot, @@ -35,6 +34,8 @@ use crate::{ Shell, }; +use proxmox_backup::backup::load_and_decrypt_key; + use crate::key::get_encryption_key_password; #[api( diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs index b0218975..878566c5 100644 --- a/src/bin/proxmox_backup_client/key.rs +++ b/src/bin/proxmox_backup_client/key.rs @@ -33,6 +33,12 @@ pub fn place_default_encryption_key() -> Result { super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME, "default encryption key file") } +pub fn read_optional_default_encryption_key() -> Result>, Error> { + find_default_encryption_key()? + .map(file_get_contents) + .transpose() +} + pub fn get_encryption_key_password() -> Result, Error> { // fixme: implement other input methods