From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 9976F76E2B for ; Mon, 19 Jul 2021 11:21:57 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8333623D65 for ; Mon, 19 Jul 2021 11:21:27 +0200 (CEST) Received: from elsa.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP id A71A923D50 for ; Mon, 19 Jul 2021 11:21:25 +0200 (CEST) Received: by elsa.proxmox.com (Postfix, from userid 0) id 668AAAE0D7D; Mon, 19 Jul 2021 11:21:19 +0200 (CEST) From: Dietmar Maurer To: pbs-devel@lists.proxmox.com Date: Mon, 19 Jul 2021 11:21:13 +0200 Message-Id: <20210719092114.4073945-1-dietmar@proxmox.com> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.476 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup 1/2] support more ENV vars to get secret values X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 19 Jul 2021 09:21:57 -0000 --- src/bin/proxmox_client_tools/key_source.rs | 9 +-- src/bin/proxmox_client_tools/mod.rs | 76 +++++++++++++++++++--- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/bin/proxmox_client_tools/key_source.rs b/src/bin/proxmox_client_tools/key_source.rs index fee00723..a611cd76 100644 --- a/src/bin/proxmox_client_tools/key_source.rs +++ b/src/bin/proxmox_client_tools/key_source.rs @@ -343,13 +343,8 @@ pub(crate) unsafe fn set_test_default_master_pubkey(value: Result pub fn get_encryption_key_password() -> Result, 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/src/bin/proxmox_client_tools/mod.rs b/src/bin/proxmox_client_tools/mod.rs index 77c33ff0..e4dfb859 100644 --- a/src/bin/proxmox_client_tools/mod.rs +++ b/src/bin/proxmox_client_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::Read; +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, }; @@ -34,6 +40,66 @@ pub const CHUNK_SIZE_SCHEMA: Schema = IntegerSchema::new("Chunk size in KB. Must .default(4096) .schema(); +/// Helper to read secrets from ENV vars. Reads the following VARs: +/// +/// BASE_NAME => use value from ENV(BASE_NAME) +/// BASE_NAME_FD => read from specified file descriptor +/// BASE_NAME_FILE => read from specified file name +/// BASE_NAME_CMD => read from specified file name +/// +/// Only return the first line when reading from a file or command. +pub fn get_secret_from_env(base_name: &str) -> Result, Error> { + + match std::env::var(base_name) { + Ok(p) => return Ok(Some(p)), + Err(NotUnicode(_)) => bail!(format!("{} contains bad characters", base_name)), + Err(NotPresent) => {}, + }; + + let firstline = |data: String| -> String { + match data.lines().next() { + Some(line) => line.to_string(), + None => String::new(), + } + }; + + 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) }; + let mut buffer = String::new(); + let _ = file.read_to_string(&mut buffer)?; + return Ok(Some(firstline(buffer))); + } + _ => {} + } + + let env_name = format!("{}_FILE", base_name); + match std::env::var(&env_name) { + Ok(filename) => { + let data = proxmox::tools::fs::file_read_string(filename)?; + return Ok(Some(firstline(data))); + } + _ => {} + } + + 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 = tools::run_command(command, None)?; + return Ok(Some(firstline(output))); + } + _ => {} + } + + Ok(None) +} + pub fn get_default_repository() -> Option { std::env::var("PBS_REPOSITORY").ok() } @@ -66,13 +132,7 @@ pub fn connect(repo: &BackupRepository) -> Result { fn connect_do(server: &str, port: u16, auth_id: &Authid) -> Result { 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) @@ -82,7 +142,7 @@ fn connect_do(server: &str, port: u16, auth_id: &Authid) -> Result 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