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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7BF7877805 for ; Wed, 21 Jul 2021 11:59:58 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7128F12916 for ; Wed, 21 Jul 2021 11:59:58 +0200 (CEST) Received: from elsa.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP id 679CD12902 for ; Wed, 21 Jul 2021 11:59:57 +0200 (CEST) Received: by elsa.proxmox.com (Postfix, from userid 0) id 495C3AE0A15; Wed, 21 Jul 2021 11:59:57 +0200 (CEST) From: Dietmar Maurer To: pbs-devel@lists.proxmox.com Date: Wed, 21 Jul 2021 11:59:54 +0200 Message-Id: <20210721095955.841384-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.451 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mod.rs] Subject: [pbs-devel] [PATCH proxmox-backup v2 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: Wed, 21 Jul 2021 09:59:58 -0000 --- 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 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/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, 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 { + 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 { std::env::var("PBS_REPOSITORY").ok() } @@ -64,13 +144,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) @@ -80,7 +154,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