support new ENV vars to get secret values through a file or a command
We want to allow passing a secret not only directly through the environment value, but also indirectly through a file path, an open file descriptor or a command that can write it to standard out. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
86b8ba448c
commit
16a01c19dd
@ -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> {
|
pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
|
||||||
// fixme: implement other input methods
|
// fixme: implement other input methods
|
||||||
|
|
||||||
use std::env::VarError::*;
|
if let Some(password) = super::get_secret_from_env("PBS_ENCRYPTION_PASSWORD")? {
|
||||||
match std::env::var("PBS_ENCRYPTION_PASSWORD") {
|
return Ok(password.as_bytes().to_vec());
|
||||||
Ok(p) => return Ok(p.as_bytes().to_vec()),
|
|
||||||
Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
|
|
||||||
Err(NotPresent) => {
|
|
||||||
// Try another method
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're on a TTY, query the user for a password
|
// If we're on a TTY, query the user for a password
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
//! Shared tools useful for common CLI clients.
|
//! Shared tools useful for common CLI clients.
|
||||||
use std::collections::HashMap;
|
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 anyhow::{bail, format_err, Context, Error};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@ -7,6 +12,7 @@ use xdg::BaseDirectories;
|
|||||||
|
|
||||||
use proxmox::{
|
use proxmox::{
|
||||||
api::schema::*,
|
api::schema::*,
|
||||||
|
api::cli::shellword_split,
|
||||||
tools::fs::file_get_json,
|
tools::fs::file_get_json,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,6 +38,80 @@ pub const CHUNK_SIZE_SCHEMA: Schema = IntegerSchema::new("Chunk size in KB. Must
|
|||||||
.default(4096)
|
.default(4096)
|
||||||
.schema();
|
.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> {
|
pub fn get_default_repository() -> Option<String> {
|
||||||
std::env::var("PBS_REPOSITORY").ok()
|
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> {
|
fn connect_do(server: &str, port: u16, auth_id: &Authid) -> Result<HttpClient, Error> {
|
||||||
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
||||||
|
|
||||||
use std::env::VarError::*;
|
let password = get_secret_from_env(ENV_VAR_PBS_PASSWORD)?;
|
||||||
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 options = HttpClientOptions::new_interactive(password, fingerprint);
|
let options = HttpClientOptions::new_interactive(password, fingerprint);
|
||||||
|
|
||||||
HttpClient::new(server, port, auth_id, options)
|
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 {
|
pub async fn try_get(repo: &BackupRepository, url: &str) -> Value {
|
||||||
|
|
||||||
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
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
|
// ticket cache, but no questions asked
|
||||||
let options = HttpClientOptions::new_interactive(password, fingerprint)
|
let options = HttpClientOptions::new_interactive(password, fingerprint)
|
||||||
|
Loading…
Reference in New Issue
Block a user