[pbs-devel] [PATCH proxmox-backup v2 1/2] support more ENV vars to get secret values

Dietmar Maurer dietmar at proxmox.com
Wed Jul 21 11:59:54 CEST 2021


---

Changes in v2:

- always use the first line
- only read the first line (not whole files)
- improve error handling
- improve docs

 pbs-client/src/tools/key_source.rs |  9 +--
 pbs-client/src/tools/mod.rs        | 90 +++++++++++++++++++++++++++---
 2 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/pbs-client/src/tools/key_source.rs b/pbs-client/src/tools/key_source.rs
index 6dade75e..a3a2bf1a 100644
--- a/pbs-client/src/tools/key_source.rs
+++ b/pbs-client/src/tools/key_source.rs
@@ -343,13 +343,8 @@ pub(crate) unsafe fn set_test_default_master_pubkey(value: Result<Option<Vec<u8>
 pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
     // fixme: implement other input methods
 
-    use std::env::VarError::*;
-    match std::env::var("PBS_ENCRYPTION_PASSWORD") {
-        Ok(p) => return Ok(p.as_bytes().to_vec()),
-        Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
-        Err(NotPresent) => {
-            // Try another method
-        }
+    if let Some(password) = super::get_secret_from_env("PBS_ENCRYPTION_PASSWORD")? {
+        return Ok(password.as_bytes().to_vec());
     }
 
     // If we're on a TTY, query the user for a password
diff --git a/pbs-client/src/tools/mod.rs b/pbs-client/src/tools/mod.rs
index 7b932b63..2c1c6e6c 100644
--- a/pbs-client/src/tools/mod.rs
+++ b/pbs-client/src/tools/mod.rs
@@ -1,5 +1,10 @@
 //! Shared tools useful for common CLI clients.
 use std::collections::HashMap;
+use std::fs::File;
+use std::os::unix::io::FromRawFd;
+use std::env::VarError::{NotUnicode, NotPresent};
+use std::io::{BufReader, BufRead};
+use std::process::Command;
 
 use anyhow::{bail, format_err, Context, Error};
 use serde_json::{json, Value};
@@ -7,6 +12,7 @@ use xdg::BaseDirectories;
 
 use proxmox::{
     api::schema::*,
+    api::cli::shellword_split,
     tools::fs::file_get_json,
 };
 
@@ -32,6 +38,80 @@ pub const CHUNK_SIZE_SCHEMA: Schema = IntegerSchema::new("Chunk size in KB. Must
     .default(4096)
     .schema();
 
+/// Helper to read a secret through a environment variable (ENV).
+///
+/// Tries the following variable names in order and returns the value
+/// it will resolve for the first defined one:
+///
+/// BASE_NAME => use value from ENV(BASE_NAME) directly as secret
+/// BASE_NAME_FD => read the secret from the specified file descriptor
+/// BASE_NAME_FILE => read the secret from the specified file name
+/// BASE_NAME_CMD => read the secret from specified command first line of output on stdout
+///
+/// Only return the first line of data (without CRLF).
+pub fn get_secret_from_env(base_name: &str) -> Result<Option<String>, Error> {
+
+    let firstline = |data: String| -> String {
+        match data.lines().next() {
+            Some(line) => line.to_string(),
+            None => String::new(),
+        }
+    };
+
+    let firstline_file = |file: &mut File| -> Result<String, Error> {
+        let reader = BufReader::new(file);
+        match reader.lines().next() {
+            Some(Ok(line)) => Ok(line),
+            Some(Err(err)) => Err(err.into()),
+            None => Ok(String::new()),
+        }
+    };
+
+    match std::env::var(base_name) {
+        Ok(p) => return Ok(Some(firstline(p))),
+        Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", base_name)),
+        Err(NotPresent) => {},
+    };
+
+    let env_name = format!("{}_FD", base_name);
+    match std::env::var(&env_name) {
+        Ok(fd_str) => {
+            let fd: i32 = fd_str.parse()
+                .map_err(|err| format_err!("unable to parse file descriptor in ENV({}): {}", env_name, err))?;
+            let mut file = unsafe { File::from_raw_fd(fd) };
+            return Ok(Some(firstline_file(&mut file)?));
+        }
+        Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", env_name)),
+        Err(NotPresent) => {},
+    }
+
+    let env_name = format!("{}_FILE", base_name);
+    match std::env::var(&env_name) {
+        Ok(filename) => {
+            let mut file = std::fs::File::open(filename)
+                .map_err(|err| format_err!("unable to open file in ENV({}): {}", env_name, err))?;
+            return Ok(Some(firstline_file(&mut file)?));
+        }
+        Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", env_name)),
+        Err(NotPresent) => {},
+    }
+
+    let env_name = format!("{}_CMD", base_name);
+    match std::env::var(&env_name) {
+        Ok(ref command) => {
+            let args = shellword_split(command)?;
+            let mut command = Command::new(&args[0]);
+            command.args(&args[1..]);
+            let output = pbs_tools::run_command(command, None)?;
+            return Ok(Some(firstline(output)));
+        }
+        Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", env_name)),
+        Err(NotPresent) => {},
+    }
+
+    Ok(None)
+}
+
 pub fn get_default_repository() -> Option<String> {
     std::env::var("PBS_REPOSITORY").ok()
 }
@@ -64,13 +144,7 @@ pub fn connect(repo: &BackupRepository) -> Result<HttpClient, Error> {
 fn connect_do(server: &str, port: u16, auth_id: &Authid) -> Result<HttpClient, Error> {
     let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
 
-    use std::env::VarError::*;
-    let password = match std::env::var(ENV_VAR_PBS_PASSWORD) {
-        Ok(p) => Some(p),
-        Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", ENV_VAR_PBS_PASSWORD)),
-        Err(NotPresent) => None,
-    };
-
+    let password = get_secret_from_env(ENV_VAR_PBS_PASSWORD)?;
     let options = HttpClientOptions::new_interactive(password, fingerprint);
 
     HttpClient::new(server, port, auth_id, options)
@@ -80,7 +154,7 @@ fn connect_do(server: &str, port: u16, auth_id: &Authid) -> Result<HttpClient, E
 pub async fn try_get(repo: &BackupRepository, url: &str) -> Value {
 
     let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
-    let password = std::env::var(ENV_VAR_PBS_PASSWORD).ok();
+    let password = get_secret_from_env(ENV_VAR_PBS_PASSWORD).unwrap_or(None);
 
     // ticket cache, but no questions asked
     let options = HttpClientOptions::new_interactive(password, fingerprint)
-- 
2.30.2





More information about the pbs-devel mailing list