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]] [[package]]
name = "signer" name = "signer"
version = "0.0.20" version = "0.0.21"
dependencies = [ dependencies = [
"base64", "base64",
"chacha20poly1305", "chacha20poly1305",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "signer" name = "signer"
version = "0.0.20" version = "0.0.21"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
description = "Attended Nostr signing daemon — Rust port of n_signer" 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; pub use error::SignerError;
/// Version string (matches C NSIGNER_VERSION). /// 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, key_store: &mut key_store,
alg_key_cache: &mut alg_key_cache, 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(); server.stop();
} else { } else {
// Tcp / Http poll loop // 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, alg_key_cache: &mut alg_key_cache,
}; };
match server.handle_one(&mut dispatcher) { match server.handle_one(&mut dispatcher) {
Ok(Some(_activity)) => {} Ok(Some(activity)) => {
println!("{}", activity);
}
Ok(None) => { Ok(None) => {
std::thread::sleep(std::time::Duration::from_millis(50)); std::thread::sleep(std::time::Duration::from_millis(50));
} }
+58 -12
View File
@@ -62,6 +62,38 @@ impl CallerIdentity {
auth_label: String::new(), 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. /// Server context.
@@ -294,7 +326,7 @@ impl ServerContext {
Err((code, msg)) => { Err((code, msg)) => {
if self.auth_mode == AuthMode::Required { if self.auth_mode == AuthMode::Required {
let response = make_auth_error(&request, code, msg); 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); return (response, activity);
} }
// Optional: continue without auth // Optional: continue without auth
@@ -308,7 +340,7 @@ impl ServerContext {
None => { None => {
// Malformed request — let the dispatcher produce the error // Malformed request — let the dispatcher produce the error
let response = crate::dispatcher::handle_request(dispatcher, request); 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); return (response, activity);
} }
}; };
@@ -316,7 +348,7 @@ impl ServerContext {
// get_info is metadata — no key material // get_info is metadata — no key material
if method == crate::enforcement::VERB_GET_INFO { if method == crate::enforcement::VERB_GET_INFO {
let response = crate::dispatcher::handle_request(dispatcher, request); 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); return (response, activity);
} }
@@ -327,9 +359,9 @@ impl ServerContext {
// standard derivation path identifies the key material. // standard derivation path identifies the key material.
let (alg_name, path) = extract_algorithm_and_path(request); let (alg_name, path) = extract_algorithm_and_path(request);
let activity = match (alg_name, path) { let activity = match (alg_name, path) {
(Some(a), Some(p)) => format!("{} {}({},{} {})", caller.caller_id, method, a, selector_req.index, p), (Some(a), Some(p)) => format!("{} {}({},{} {})", caller.identity_str(), method, a, selector_req.index, p),
(Some(a), None) => format!("{} {}({})", caller.caller_id, method, a), (Some(a), None) => format!("{} {}({})", caller.identity_str(), method, a),
_ => format!("{} {}()", caller.caller_id, method), _ => format!("{} {}()", caller.identity_str(), method),
}; };
return (response, activity); return (response, activity);
} }
@@ -337,7 +369,7 @@ impl ServerContext {
// OTP verbs // OTP verbs
if method == crate::enforcement::VERB_ENCRYPT || method == crate::enforcement::VERB_DECRYPT { if method == crate::enforcement::VERB_ENCRYPT || method == crate::enforcement::VERB_DECRYPT {
let response = crate::dispatcher::handle_request(dispatcher, request); 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); return (response, activity);
} }
@@ -349,7 +381,7 @@ impl ServerContext {
let response = make_selector_error(&request, e); let response = make_selector_error(&request, e);
let activity = format!( let activity = format!(
"{} {}() DENIED:{}", "{} {}() DENIED:{}",
caller.caller_id, caller.identity_str(),
method, method,
e.as_str() e.as_str()
); );
@@ -358,15 +390,29 @@ impl ServerContext {
}; };
// Role entry from the resolved selector — used for the activity // 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_entry = &dispatcher.role_table.entries[role_index];
let role_name = role_entry.name.clone();
let curve = role_entry.curve_str.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 ─────────────────────────────────────────────── // ── Dispatch ───────────────────────────────────────────────
let response = crate::dispatcher::handle_request(dispatcher, request); let response = crate::dispatcher::handle_request(dispatcher, request);
// Activity format: uid curve path (timestamp is added by the log). // Activity format: uid role curve path (timestamp is added by the log).
let activity = format!("{} {} {}", caller.caller_id, curve, path); let activity = format!(
"{} {} {} {}",
caller.identity_str(),
role_name,
curve,
actual_path
);
(response, activity) (response, activity)
} }