feat(app): allow hexadecimal HMAC key specification and redact keys from debug output

This commit is contained in:
Nils Andreas Svee committed 2026-10-21 01:19:59 +07:00
1 parent ab241c4a2b
commit 18a8e307fb
14 files changed
+72 -40

No files matched your search

+3 -9
View File
@@ -26,8 +26,9 @@ pub struct CommonClientArgs {
#[arg(long, default_value_t = 0, value_parser = parse_length)]
pub length: u32,
/// HMAC key.
#[arg(long, value_parser = parse_hmac_key)]
// Decode during configuration preparation so Clap cannot echo rejected keys.
/// HMAC key: literal UTF-8 or lowercase 0x followed by hexadecimal bytes.
#[arg(long)]
pub hmac: Option<String>,
/// Clock mode to request.
@@ -267,13 +268,6 @@ pub fn parse_length(input: &str) -> Result<u32, String> {
Ok(length)
}
pub fn parse_hmac_key(input: &str) -> Result<String, String> {
if input.is_empty() {
return Err("HMAC key must not be empty".to_owned());
}
Ok(input.to_owned())
}
pub fn parse_server_fill(input: &str) -> Result<String, String> {
if input.is_empty() {
return Err("server fill must not be empty".to_owned());
+7 -5
View File
@@ -21,8 +21,8 @@ impl CommonClientArgs {
/// Build the shared client configuration template for a managed run.
///
/// The config is endpoint-independent; managed targets own their addresses.
pub fn to_client_config(&self, duration: Duration) -> ClientConfig {
ClientConfig {
pub fn to_client_config(&self, duration: Duration) -> Result<ClientConfig, String> {
Ok(ClientConfig {
address_family: self.address_family(),
open: OpenPolicy {
negotiation: if self.loose {
@@ -45,9 +45,11 @@ impl CommonClientArgs {
},
auth: self
.hmac
.as_ref()
.as_deref()
.map(crate::shared::decode_hmac_key)
.transpose()?
.map_or(Authentication::Unauthenticated, |key| {
Authentication::Hmac(HmacKey::new(key.as_bytes()))
Authentication::Hmac(HmacKey::new(key))
}),
socket: SocketConfig {
bind_addr: self.bind_addr,
@@ -61,7 +63,7 @@ impl CommonClientArgs {
..SocketConfig::default()
},
..ClientConfig::default()
}
})
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ pub async fn prepare_managed_run(
.await?;
Ok(ManagedRunSetup {
targets,
client: common.to_client_config(duration),
client: common.to_client_config(duration)?,
pacing: selection.pacing.into(),
stdin_controlled: selection.stdin_controlled,
preparation,
+7 -2
View File
@@ -88,12 +88,13 @@ impl From<GroupPacingArg> for ManagedPacing {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
enum TargetParseError {
EmptyLabel,
EmptyAddress,
InvalidEscape,
EmptyTarget,
InvalidHmac(String),
}
impl fmt::Display for TargetParseError {
@@ -103,6 +104,7 @@ impl fmt::Display for TargetParseError {
Self::EmptyAddress => "target address must not be empty",
Self::InvalidEscape => "invalid target escape sequence",
Self::EmptyTarget => "target must not be empty",
Self::InvalidHmac(message) => message,
};
f.write_str(message)
}
@@ -197,7 +199,10 @@ fn parse_target_syntax(
let auth = if value.is_empty() {
TargetAuth::Override(Authentication::Unauthenticated)
} else {
TargetAuth::Override(Authentication::Hmac(HmacKey::new(value.into_bytes())))
TargetAuth::Override(Authentication::Hmac(HmacKey::new(
crate::shared::decode_hmac_key(&value)
.map_err(TargetParseError::InvalidHmac)?,
)))
};
(&input[..index], auth)
}
+14
View File
@@ -0,0 +1,14 @@
/// Decode CLI key syntax without ever including key material in diagnostics.
pub(crate) fn decode_hmac_key(input: &str) -> Result<Vec<u8>, String> {
let key = match input.strip_prefix("0x") {
Some(hex) => hex::decode(hex).map_err(|_| {
"HMAC hexadecimal key must contain an even number of valid hexadecimal digits"
.to_owned()
})?,
None => input.as_bytes().to_vec(),
};
if key.is_empty() {
return Err("HMAC key must not be empty".to_owned());
}
Ok(key)
}
+5
View File
@@ -1,2 +1,7 @@
#[cfg(any(feature = "client", feature = "tui"))]
pub mod client;
#[cfg(any(feature = "client", feature = "tui", feature = "server"))]
mod hmac;
#[cfg(any(feature = "client", feature = "tui", feature = "server"))]
pub(crate) use hmac::decode_hmac_key;