feat(rpc): auth handshake, server-push broker, audit logging
- auth_handshake.rs: connection-init protocol (magic 0x01, token, ack) - push.rs: PushBroker manages per-identity push connections with gc - server.rs: ConnectionState, auth handshake on first bi-stream, pass identity_key/session_token to RequestContext per stream - client.rs: session_token in RpcClientConfig, auto auth handshake on connect - middleware.rs: log_rpc_call with SHA-256 redaction, hex_prefix helper - lib.rs: export auth_handshake and push modules
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
//! Tower-based middleware layers for the RPC server.
|
||||
//! Middleware layers for the RPC server.
|
||||
//!
|
||||
//! - `AuthLayer`: validates session tokens and attaches identity to context.
|
||||
//! - `RateLimitLayer`: per-IP request rate limiting.
|
||||
//! - `SessionValidator`: validates session tokens and resolves identity keys.
|
||||
//! - `RateLimiter`: per-key sliding-window rate limiting.
|
||||
//! - `log_rpc_call`: structured audit logging for RPC calls.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::error::RpcStatus;
|
||||
|
||||
// ── Auth middleware ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -70,6 +74,45 @@ impl RateLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Audit logging ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Log an RPC call with timing and caller info.
|
||||
///
|
||||
/// When `redact` is true, the identity key is hashed before logging so that
|
||||
/// raw keys never appear in log output.
|
||||
pub fn log_rpc_call(
|
||||
method_name: &str,
|
||||
identity_key: Option<&[u8]>,
|
||||
latency: Duration,
|
||||
status: RpcStatus,
|
||||
redact: bool,
|
||||
) {
|
||||
let ik_display = match identity_key {
|
||||
Some(ik) if redact => {
|
||||
let hash_input_len = 8.min(ik.len());
|
||||
let digest = sha2::Sha256::digest(&ik[..hash_input_len]);
|
||||
format!("h:{}", hex_prefix(&digest))
|
||||
}
|
||||
Some(ik) => hex_prefix(ik),
|
||||
None => "anonymous".to_string(),
|
||||
};
|
||||
tracing::info!(
|
||||
method = method_name,
|
||||
identity = %ik_display,
|
||||
latency_ms = latency.as_millis() as u64,
|
||||
status = ?status,
|
||||
"rpc"
|
||||
);
|
||||
}
|
||||
|
||||
fn hex_prefix(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
.take(4)
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -93,4 +136,19 @@ mod tests {
|
||||
std::thread::sleep(Duration::from_millis(5));
|
||||
assert!(rl.check(key)); // window expired
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_prefix_formats_first_4_bytes() {
|
||||
assert_eq!(hex_prefix(&[0xab, 0xcd, 0xef, 0x01, 0x99]), "abcdef01");
|
||||
assert_eq!(hex_prefix(&[0x00, 0xff]), "00ff");
|
||||
assert_eq!(hex_prefix(&[]), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_rpc_call_does_not_panic() {
|
||||
// Verify that audit log function does not panic with various inputs.
|
||||
log_rpc_call("test.method", None, Duration::from_millis(42), RpcStatus::Ok, false);
|
||||
log_rpc_call("test.method", Some(&[1, 2, 3, 4, 5, 6, 7, 8]), Duration::from_millis(1), RpcStatus::Internal, true);
|
||||
log_rpc_call("test.method", Some(&[0xab]), Duration::ZERO, RpcStatus::Unauthorized, true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user