From 1fefdecbe7b03f1cf7c53b610a6faf65559bb076 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 23 Apr 2025 11:24:28 +0200 Subject: [PATCH] pvsecret: Support retrieving a secret by index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow users to retrieve a secret by its index. This allows users to extract a specific secret in case of multiple ids. Reviewed-by: Finn Callies Tested-by: Finn Callies Signed-off-by: Steffen Eiden Signed-off-by: Jan Höppner --- rust/pvsecret/src/cli.rs | 2 + rust/pvsecret/src/cmd/retr.rs | 86 +++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs index 74f6bad2..6b983eb4 100644 --- a/rust/pvsecret/src/cli.rs +++ b/rust/pvsecret/src/cli.rs @@ -351,6 +351,8 @@ pub enum RetrInpFmt { Hex, /// Use a name-string. Will hash it if no secret with the name found. Name, + /// Use the secret-index (base 10) instead of the secret-ID + Idx, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)] diff --git a/rust/pvsecret/src/cmd/retr.rs b/rust/pvsecret/src/cmd/retr.rs index 69484604..0a3a53f1 100644 --- a/rust/pvsecret/src/cmd/retr.rs +++ b/rust/pvsecret/src/cmd/retr.rs @@ -2,7 +2,7 @@ // // Copyright IBM Corp. 2024 -use std::collections::VecDeque; +use std::{collections::VecDeque, fmt::Display}; use anyhow::{anyhow, bail, Context, Result}; use log::{debug, info, warn}; @@ -17,6 +17,42 @@ use utils::get_writer_from_cli_file_arg; use super::list::list_uvc; use crate::cli::{RetrInpFmt, RetrOutFmt, RetrSecretOptions}; +enum Value { + Id(SecretId), + Idx(u16), +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Id(id) => write!(f, "ID {id}"), + Value::Idx(idx) => write!(f, "index {idx}"), + } + } +} + +impl TryFrom<&RetrSecretOptions> for Value { + type Error = anyhow::Error; + + fn try_from(opt: &RetrSecretOptions) -> Result { + match opt.inform { + RetrInpFmt::Yaml => match serde_yaml::from_reader(&mut open_file(&opt.input)?)? { + GuestSecret::Retrievable { id, .. } => Ok(Self::Id(id)), + gs => bail!("The file contains a {gs}-secret, which is not retrievable."), + }, + RetrInpFmt::Hex => serde_yaml::from_str(&opt.input) + .context("Cannot parse SecretId information") + .map(Self::Id), + RetrInpFmt::Name => Ok(Self::Id(SecretId::from_string(&opt.input))), + RetrInpFmt::Idx => opt + .input + .parse() + .context("Invalid index value") + .map(Self::Idx), + } + } +} + fn find_secret_by_id(secrets: &SecretList, id: &SecretId) -> Option { let mut secrets: VecDeque<_> = secrets .into_iter() @@ -37,26 +73,31 @@ fn find_secret_by_id(secrets: &SecretList, id: &SecretId) -> Option secret.cloned() } -fn retrieve(id: &SecretId) -> Result { +fn retrieve(value: Value) -> Result { let uv = UvDevice::open()?; let secrets = list_uvc(&uv)?; - let secret = match find_secret_by_id(&secrets, id) { - Some(s) => s, - // hash it + try again if it is ASCII-representable - None => match id.as_ascii() { - Some(s) => find_secret_by_id(&secrets, &GuestSecret::name_to_id(s)?), - None => None, + let entry = match &value { + Value::Id(id) => { + match find_secret_by_id(&secrets, id) { + Some(s) => Some(s), + // hash it + try again if it is ASCII-representable + None => match id.as_ascii() { + Some(s) => find_secret_by_id(&secrets, &GuestSecret::name_to_id(s)?), + None => None, + }, + } } - .ok_or(anyhow!( - "The UV secret-store has no secret with the ID {id}" - ))?, - }; + Value::Idx(idx) => secrets.into_iter().find(|e| &e.index() == idx), + } + .ok_or(anyhow!( + "The UV secret-store has no secret with the {value}" + ))?; - info!("Try to retrieve secret at index: {}", secret.index()); - debug!("Try to retrieve: {secret:?}"); + info!("Try to retrieve secret at index: {}", entry.index()); + debug!("Try to retrieve: {entry:?}"); - let mut uv_cmd = RetrieveCmd::from_entry(secret)?; + let mut uv_cmd = RetrieveCmd::from_entry(entry)?; uv.send_cmd(&mut uv_cmd)?; Ok(RetrievedSecret::from_cmd(uv_cmd)) @@ -64,19 +105,8 @@ fn retrieve(id: &SecretId) -> Result { pub fn retr(opt: &RetrSecretOptions) -> Result<()> { let mut output = get_writer_from_cli_file_arg(&opt.output)?; - let id = match &opt.inform { - RetrInpFmt::Yaml => match serde_yaml::from_reader(&mut open_file(&opt.input)?)? { - GuestSecret::Retrievable { id, .. } => id, - gs => bail!("The file contains a {gs}-secret, which is not retrievable."), - }, - RetrInpFmt::Hex => { - serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")? - } - RetrInpFmt::Name => SecretId::from_string(&opt.input), - }; - - let retr_secret = - retrieve(&id).context("Could not retrieve the secret from the UV secret store.")?; + let retr_secret = retrieve(opt.try_into()?) + .context("Could not retrieve the secret from the UV secret store.")?; let out_data = match opt.outform { RetrOutFmt::Bin => retr_secret.into_bytes(),