]> git.proxmox.com Git - proxmox-backup.git/commitdiff
client: introduce --keyfd parameter
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 8 Jul 2020 11:52:17 +0000 (13:52 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 8 Jul 2020 11:56:38 +0000 (13:56 +0200)
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 <w.bumiller@proxmox.com>
src/backup/key_derivation.rs
src/bin/proxmox-backup-client.rs
src/bin/proxmox_backup_client/catalog.rs
src/bin/proxmox_backup_client/key.rs

index 7dd23e67fb37348dfa5f79e0e9fb42afb59e4a14..4befe525e77751e9898a28ef2b4009a3a81bcf71 100644 (file)
@@ -158,10 +158,14 @@ fn do_load_and_decrypt_key(
     path: &std::path::Path,
     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
 ) -> Result<([u8;32], DateTime<Local>), 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<Vec<u8>, Error>,
+) -> Result<([u8;32], DateTime<Local>), Error> {
+    let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
 
     let raw_data = key_config.data;
     let created = key_config.created;
index a6cbcc1eed0fcfccf8c4deb0275ced4dccfde408..7d4f28fa6ccac74be1c8d19e8cc071634b9a87dd 100644 (file)
@@ -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<PathBuf>, CryptMode), Error> {
+fn keyfile_parameters(param: &Value) -> Result<(Option<Vec<u8>>, 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<CryptMode> = 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<PathBuf>, 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<PathBuf>, 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(&param)?;
+    let (keydata, crypt_mode) = keyfile_parameters(&param)?;
 
     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<Value, Error> {
     let target = tools::required_string_param(&param, "target")?;
     let target = if target == "-" { None } else { Some(target) };
 
-    let (keyfile, _crypt_mode) = keyfile_parameters(&param)?;
+    let (keydata, _crypt_mode) = keyfile_parameters(&param)?;
 
-    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<Value, Error> {
                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<Value, Error> {
 
     let mut client = connect(repo.host(), repo.user())?;
 
-    let (keyfile, crypt_mode) = keyfile_parameters(&param)?;
+    let (keydata, crypt_mode) = keyfile_parameters(&param)?;
 
-    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<io::Result<usize>, 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))?;
index 595b5bab08bf0e6297a85f75fad51a7436b0e0af..ecc1278be267f43ff86f1041d0eab0ccbabb25f2 100644 (file)
@@ -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(
index b0218975a89c03f7da20b54ee2c732ae466fe550..878566c5b4e9f83c1874ac9dcec8420f85935acb 100644 (file)
@@ -33,6 +33,12 @@ pub fn place_default_encryption_key() -> Result<PathBuf, Error> {
     super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME, "default encryption key file")
 }
 
+pub fn read_optional_default_encryption_key() -> Result<Option<Vec<u8>>, Error> {
+    find_default_encryption_key()?
+        .map(file_get_contents)
+        .transpose()
+}
+
 pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
     // fixme: implement other input methods