v0.0.21 - Activity log: show caller identity, role, curve, and actual requested key path

This commit is contained in:
Laan Tungir
2026-08-22 07:57:19 -04:00
parent b2f5d06570
commit 782716c53b
5 changed files with 67 additions and 17 deletions
Generated
+1 -1
View File
@@ -2207,7 +2207,7 @@ dependencies = [
[[package]]
name = "signer"
version = "0.0.20"
version = "0.0.21"
dependencies = [
"base64",
"chacha20poly1305",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "signer"
version = "0.0.20"
version = "0.0.21"
edition = "2021"
license = "MIT"
description = "Attended Nostr signing daemon — Rust port of n_signer"
+1 -1
View File
@@ -31,4 +31,4 @@ pub mod error;
pub use error::SignerError;
/// Version string (matches C NSIGNER_VERSION).
pub const VERSION: &str = "v0.0.20";
pub const VERSION: &str = "v0.0.21";
+6 -2
View File
@@ -241,7 +241,9 @@ fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), SignerError> {
key_store: &mut key_store,
alg_key_cache: &mut alg_key_cache,
};
let _ = server.handle_one(&mut dispatcher);
if let Ok(Some(activity)) = server.handle_one(&mut dispatcher) {
println!("{}", activity);
}
server.stop();
} else {
// Tcp / Http poll loop
@@ -253,7 +255,9 @@ fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), SignerError> {
alg_key_cache: &mut alg_key_cache,
};
match server.handle_one(&mut dispatcher) {
Ok(Some(_activity)) => {}
Ok(Some(activity)) => {
println!("{}", activity);
}
Ok(None) => {
std::thread::sleep(std::time::Duration::from_millis(50));
}
+58 -12
View File
@@ -62,6 +62,38 @@ impl CallerIdentity {
auth_label: String::new(),
}
}
/// Build a rich identity string for the activity log — as much as can be
/// identified about the caller.
///
/// - Unix socket: `uid:<n> gid:<n> pid:<n>`
/// - Qrexec: `qubes:<vm>`
/// - TCP/HTTP: `tcp:<addr>`
/// - Auth envelope verified: `pubkey:<hex> label:<label>` appended.
pub fn identity_str(&self) -> String {
let mut parts: Vec<String> = Vec::new();
match self.kind {
ListenMode::Unix => {
parts.push(format!("uid:{}", self.uid));
if self.gid != 0 {
parts.push(format!("gid:{}", self.gid));
}
if self.pid != 0 {
parts.push(format!("pid:{}", self.pid));
}
}
_ => {
parts.push(self.caller_id.clone());
}
}
if self.auth_present {
parts.push(format!("pubkey:{}", self.auth_pubkey_hex));
if !self.auth_label.is_empty() {
parts.push(format!("label:{}", self.auth_label));
}
}
parts.join(" ")
}
}
/// Server context.
@@ -294,7 +326,7 @@ impl ServerContext {
Err((code, msg)) => {
if self.auth_mode == AuthMode::Required {
let response = make_auth_error(&request, code, msg);
let activity = format!("{} DENIED:{}", caller.caller_id, msg);
let activity = format!("{} DENIED:{}", caller.identity_str(), msg);
return (response, activity);
}
// Optional: continue without auth
@@ -308,7 +340,7 @@ impl ServerContext {
None => {
// Malformed request — let the dispatcher produce the error
let response = crate::dispatcher::handle_request(dispatcher, request);
let activity = format!("{} DENIED:malformed", caller.caller_id);
let activity = format!("{} DENIED:malformed", caller.identity_str());
return (response, activity);
}
};
@@ -316,7 +348,7 @@ impl ServerContext {
// get_info is metadata — no key material
if method == crate::enforcement::VERB_GET_INFO {
let response = crate::dispatcher::handle_request(dispatcher, request);
let activity = format!("{} {}()", caller.caller_id, method);
let activity = format!("{} {}()", caller.identity_str(), method);
return (response, activity);
}
@@ -327,9 +359,9 @@ impl ServerContext {
// standard derivation path identifies the key material.
let (alg_name, path) = extract_algorithm_and_path(request);
let activity = match (alg_name, path) {
(Some(a), Some(p)) => format!("{} {}({},{} {})", caller.caller_id, method, a, selector_req.index, p),
(Some(a), None) => format!("{} {}({})", caller.caller_id, method, a),
_ => format!("{} {}()", caller.caller_id, method),
(Some(a), Some(p)) => format!("{} {}({},{} {})", caller.identity_str(), method, a, selector_req.index, p),
(Some(a), None) => format!("{} {}({})", caller.identity_str(), method, a),
_ => format!("{} {}()", caller.identity_str(), method),
};
return (response, activity);
}
@@ -337,7 +369,7 @@ impl ServerContext {
// OTP verbs
if method == crate::enforcement::VERB_ENCRYPT || method == crate::enforcement::VERB_DECRYPT {
let response = crate::dispatcher::handle_request(dispatcher, request);
let activity = format!("{} {}()", caller.caller_id, method);
let activity = format!("{} {}()", caller.identity_str(), method);
return (response, activity);
}
@@ -349,7 +381,7 @@ impl ServerContext {
let response = make_selector_error(&request, e);
let activity = format!(
"{} {}() DENIED:{}",
caller.caller_id,
caller.identity_str(),
method,
e.as_str()
);
@@ -358,15 +390,29 @@ impl ServerContext {
};
// Role entry from the resolved selector — used for the activity
// message (curve + key path).
// message (role name, curve, and the concrete key path requested).
let role_entry = &dispatcher.role_table.entries[role_index];
let role_name = role_entry.name.clone();
let curve = role_entry.curve_str.clone();
let path = role_entry.display_path();
// The actual key path the caller requested/accessed — the concrete
// role_path from the request when supplied, otherwise the role's
// fixed/derived path. (Not the allowed range.)
let actual_path = if selector_req.has_role_path {
selector_req.role_path.clone()
} else {
role_entry.display_path()
};
// ── Dispatch ───────────────────────────────────────────────
let response = crate::dispatcher::handle_request(dispatcher, request);
// Activity format: uid curve path (timestamp is added by the log).
let activity = format!("{} {} {}", caller.caller_id, curve, path);
// Activity format: uid role curve path (timestamp is added by the log).
let activity = format!(
"{} {} {} {}",
caller.identity_str(),
role_name,
curve,
actual_path
);
(response, activity)
}