use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use super::authorization::Permission;
use super::error::SecurityError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuditEventType {
Authentication,
Authorization,
DataAccess,
DataModification,
KeyExchange,
CellFormation,
LeaderElection,
SecurityViolation,
SessionManagement,
Encryption,
}
impl std::fmt::Display for AuditEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AuditEventType::Authentication => write!(f, "AUTHENTICATION"),
AuditEventType::Authorization => write!(f, "AUTHORIZATION"),
AuditEventType::DataAccess => write!(f, "DATA_ACCESS"),
AuditEventType::DataModification => write!(f, "DATA_MODIFICATION"),
AuditEventType::KeyExchange => write!(f, "KEY_EXCHANGE"),
AuditEventType::CellFormation => write!(f, "CELL_FORMATION"),
AuditEventType::LeaderElection => write!(f, "LEADER_ELECTION"),
AuditEventType::SecurityViolation => write!(f, "SECURITY_VIOLATION"),
AuditEventType::SessionManagement => write!(f, "SESSION_MANAGEMENT"),
AuditEventType::Encryption => write!(f, "ENCRYPTION"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SecurityViolation {
InvalidSignature,
ReplayAttack,
UnauthorizedAccess,
CertificateError,
TamperedMessage,
RateLimitExceeded,
UnknownDevice,
ExpiredCredentials,
ProtocolViolation,
}
impl std::fmt::Display for SecurityViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SecurityViolation::InvalidSignature => write!(f, "INVALID_SIGNATURE"),
SecurityViolation::ReplayAttack => write!(f, "REPLAY_ATTACK"),
SecurityViolation::UnauthorizedAccess => write!(f, "UNAUTHORIZED_ACCESS"),
SecurityViolation::CertificateError => write!(f, "CERTIFICATE_ERROR"),
SecurityViolation::TamperedMessage => write!(f, "TAMPERED_MESSAGE"),
SecurityViolation::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
SecurityViolation::UnknownDevice => write!(f, "UNKNOWN_DEVICE"),
SecurityViolation::ExpiredCredentials => write!(f, "EXPIRED_CREDENTIALS"),
SecurityViolation::ProtocolViolation => write!(f, "PROTOCOL_VIOLATION"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogEntry {
pub timestamp: u64,
pub timestamp_iso: String,
pub event_type: AuditEventType,
pub entity_id: String,
pub success: bool,
pub description: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub context: HashMap<String, String>,
pub sequence: u64,
}
impl AuditLogEntry {
pub fn new(
event_type: AuditEventType,
entity_id: impl Into<String>,
success: bool,
description: impl Into<String>,
sequence: u64,
) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
Self {
timestamp: now,
timestamp_iso: format_timestamp(now),
event_type,
entity_id: entity_id.into(),
success,
description: description.into(),
context: HashMap::new(),
sequence,
}
}
pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.insert(key.into(), value.into());
self
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| {
format!(
"{{\"error\":\"serialization failed for seq {}\"}}",
self.sequence
)
})
}
}
fn format_timestamp(secs: u64) -> String {
let dt = chrono::DateTime::from_timestamp(secs as i64, 0)
.or_else(|| chrono::DateTime::from_timestamp(0, 0))
.expect("Unix epoch is always a valid timestamp");
dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()
}
pub trait AuditLogger: Send + Sync {
fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>);
fn log_grant(&self, entity_id: &str, permission: Permission, target: &str);
fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str);
fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool);
fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str);
fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool);
fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool);
fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool);
fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool);
fn entry_count(&self) -> u64;
fn flush(&self) -> Result<(), SecurityError>;
}
#[derive(Debug)]
pub struct MemoryAuditLogger {
entries: Arc<Mutex<Vec<AuditLogEntry>>>,
sequence: Arc<Mutex<u64>>,
}
impl MemoryAuditLogger {
pub fn new() -> Self {
Self {
entries: Arc::new(Mutex::new(Vec::new())),
sequence: Arc::new(Mutex::new(0)),
}
}
pub fn entries(&self) -> Vec<AuditLogEntry> {
self.entries.lock().expect("entries lock poisoned").clone()
}
pub fn entries_by_type(&self, event_type: AuditEventType) -> Vec<AuditLogEntry> {
self.entries
.lock()
.expect("entries lock poisoned")
.iter()
.filter(|e| e.event_type == event_type)
.cloned()
.collect()
}
pub fn clear(&self) {
self.entries.lock().expect("entries lock poisoned").clear();
}
fn next_sequence(&self) -> u64 {
let mut seq = self.sequence.lock().expect("sequence lock poisoned");
*seq += 1;
*seq
}
fn add_entry(&self, entry: AuditLogEntry) {
self.entries
.lock()
.expect("entries lock poisoned")
.push(entry);
}
}
impl Default for MemoryAuditLogger {
fn default() -> Self {
Self::new()
}
}
impl AuditLogger for MemoryAuditLogger {
fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>) {
let description = match (success, reason) {
(true, Some(r)) => format!("Authentication succeeded: {}", r),
(true, None) => "Authentication succeeded".to_string(),
(false, Some(r)) => format!("Authentication failed: {}", r),
(false, None) => "Authentication failed".to_string(),
};
let entry = AuditLogEntry::new(
AuditEventType::Authentication,
entity_id,
success,
description,
self.next_sequence(),
);
self.add_entry(entry);
}
fn log_grant(&self, entity_id: &str, permission: Permission, target: &str) {
let entry = AuditLogEntry::new(
AuditEventType::Authorization,
entity_id,
true,
format!("Permission granted: {:?}", permission),
self.next_sequence(),
)
.with_context("permission", format!("{:?}", permission))
.with_context("target", target);
self.add_entry(entry);
}
fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str) {
let entry = AuditLogEntry::new(
AuditEventType::Authorization,
entity_id,
false,
format!("Permission denied: {} - {}", permission, reason),
self.next_sequence(),
)
.with_context("permission", permission)
.with_context("target", target)
.with_context("reason", reason);
self.add_entry(entry);
}
fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
let event_type = if operation.starts_with("read")
|| operation.starts_with("get")
|| operation.starts_with("query")
{
AuditEventType::DataAccess
} else {
AuditEventType::DataModification
};
let entry = AuditLogEntry::new(
event_type,
entity_id,
success,
format!("{} on {}", operation, target),
self.next_sequence(),
)
.with_context("operation", operation)
.with_context("target", target);
self.add_entry(entry);
}
fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str) {
let entry = AuditLogEntry::new(
AuditEventType::SecurityViolation,
entity_id,
false,
format!("Security violation: {} - {}", violation, details),
self.next_sequence(),
)
.with_context("violation_type", violation.to_string())
.with_context("details", details);
self.add_entry(entry);
}
fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::CellFormation,
entity_id,
success,
format!("Cell {}: {}", action, cell_id),
self.next_sequence(),
)
.with_context("cell_id", cell_id)
.with_context("action", action);
self.add_entry(entry);
}
fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::KeyExchange,
entity_id,
success,
format!("Key exchange with peer: {}", peer_id),
self.next_sequence(),
)
.with_context("peer_id", peer_id);
self.add_entry(entry);
}
fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::SessionManagement,
entity_id,
success,
format!("Session {}: {}", action, session_id),
self.next_sequence(),
)
.with_context("session_id", session_id)
.with_context("action", action);
self.add_entry(entry);
}
fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::Encryption,
entity_id,
success,
format!("Encryption {}: {}", operation, target),
self.next_sequence(),
)
.with_context("operation", operation)
.with_context("target", target);
self.add_entry(entry);
}
fn entry_count(&self) -> u64 {
self.entries.lock().expect("entries lock poisoned").len() as u64
}
fn flush(&self) -> Result<(), SecurityError> {
Ok(()) }
}
pub struct FileAuditLogger {
writer: Arc<Mutex<BufWriter<File>>>,
sequence: Arc<Mutex<u64>>,
path: String,
}
impl FileAuditLogger {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, SecurityError> {
let path_str = path.as_ref().to_string_lossy().to_string();
let file = OpenOptions::new().create(true).append(true).open(&path)?;
Ok(Self {
writer: Arc::new(Mutex::new(BufWriter::new(file))),
sequence: Arc::new(Mutex::new(0)),
path: path_str,
})
}
pub fn path(&self) -> &str {
&self.path
}
fn next_sequence(&self) -> u64 {
let mut seq = self.sequence.lock().expect("sequence lock poisoned");
*seq += 1;
*seq
}
fn write_entry(&self, entry: &AuditLogEntry) {
let json = entry.to_json();
if let Ok(mut writer) = self.writer.lock() {
let _ = writeln!(writer, "{}", json);
let _ = writer.flush();
}
}
}
impl std::fmt::Debug for FileAuditLogger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileAuditLogger")
.field("path", &self.path)
.field("sequence", &self.sequence)
.finish()
}
}
impl AuditLogger for FileAuditLogger {
fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>) {
let description = match (success, reason) {
(true, Some(r)) => format!("Authentication succeeded: {}", r),
(true, None) => "Authentication succeeded".to_string(),
(false, Some(r)) => format!("Authentication failed: {}", r),
(false, None) => "Authentication failed".to_string(),
};
let entry = AuditLogEntry::new(
AuditEventType::Authentication,
entity_id,
success,
description,
self.next_sequence(),
);
self.write_entry(&entry);
}
fn log_grant(&self, entity_id: &str, permission: Permission, target: &str) {
let entry = AuditLogEntry::new(
AuditEventType::Authorization,
entity_id,
true,
format!("Permission granted: {:?}", permission),
self.next_sequence(),
)
.with_context("permission", format!("{:?}", permission))
.with_context("target", target);
self.write_entry(&entry);
}
fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str) {
let entry = AuditLogEntry::new(
AuditEventType::Authorization,
entity_id,
false,
format!("Permission denied: {} - {}", permission, reason),
self.next_sequence(),
)
.with_context("permission", permission)
.with_context("target", target)
.with_context("reason", reason);
self.write_entry(&entry);
}
fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
let event_type = if operation.starts_with("read")
|| operation.starts_with("get")
|| operation.starts_with("query")
{
AuditEventType::DataAccess
} else {
AuditEventType::DataModification
};
let entry = AuditLogEntry::new(
event_type,
entity_id,
success,
format!("{} on {}", operation, target),
self.next_sequence(),
)
.with_context("operation", operation)
.with_context("target", target);
self.write_entry(&entry);
}
fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str) {
let entry = AuditLogEntry::new(
AuditEventType::SecurityViolation,
entity_id,
false,
format!("Security violation: {} - {}", violation, details),
self.next_sequence(),
)
.with_context("violation_type", violation.to_string())
.with_context("details", details);
self.write_entry(&entry);
}
fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::CellFormation,
entity_id,
success,
format!("Cell {}: {}", action, cell_id),
self.next_sequence(),
)
.with_context("cell_id", cell_id)
.with_context("action", action);
self.write_entry(&entry);
}
fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::KeyExchange,
entity_id,
success,
format!("Key exchange with peer: {}", peer_id),
self.next_sequence(),
)
.with_context("peer_id", peer_id);
self.write_entry(&entry);
}
fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::SessionManagement,
entity_id,
success,
format!("Session {}: {}", action, session_id),
self.next_sequence(),
)
.with_context("session_id", session_id)
.with_context("action", action);
self.write_entry(&entry);
}
fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
let entry = AuditLogEntry::new(
AuditEventType::Encryption,
entity_id,
success,
format!("Encryption {}: {}", operation, target),
self.next_sequence(),
)
.with_context("operation", operation)
.with_context("target", target);
self.write_entry(&entry);
}
fn entry_count(&self) -> u64 {
*self.sequence.lock().expect("sequence lock poisoned")
}
fn flush(&self) -> Result<(), SecurityError> {
self.writer
.lock()
.map_err(|e| SecurityError::Internal(e.to_string()))?
.flush()?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct NullAuditLogger;
impl NullAuditLogger {
pub fn new() -> Self {
Self
}
}
impl AuditLogger for NullAuditLogger {
fn log_authentication(&self, _entity_id: &str, _success: bool, _reason: Option<&str>) {}
fn log_grant(&self, _entity_id: &str, _permission: Permission, _target: &str) {}
fn log_denial(&self, _entity_id: &str, _permission: &str, _target: &str, _reason: &str) {}
fn log_operation(&self, _entity_id: &str, _operation: &str, _target: &str, _success: bool) {}
fn log_violation(&self, _entity_id: &str, _violation: SecurityViolation, _details: &str) {}
fn log_cell_event(&self, _entity_id: &str, _cell_id: &str, _action: &str, _success: bool) {}
fn log_key_exchange(&self, _entity_id: &str, _peer_id: &str, _success: bool) {}
fn log_session(&self, _entity_id: &str, _session_id: &str, _action: &str, _success: bool) {}
fn log_encryption(&self, _entity_id: &str, _operation: &str, _target: &str, _success: bool) {}
fn entry_count(&self) -> u64 {
0
}
fn flush(&self) -> Result<(), SecurityError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_entry_creation() {
let entry = AuditLogEntry::new(
AuditEventType::Authentication,
"device:abc123",
true,
"Test entry",
1,
);
assert_eq!(entry.entity_id, "device:abc123");
assert!(entry.success);
assert_eq!(entry.event_type, AuditEventType::Authentication);
assert_eq!(entry.sequence, 1);
assert!(entry.timestamp > 0);
}
#[test]
fn test_audit_entry_with_context() {
let entry = AuditLogEntry::new(
AuditEventType::Authorization,
"device:abc123",
false,
"Access denied",
1,
)
.with_context("permission", "SetCellLeader")
.with_context("cell_id", "cell-1");
assert_eq!(entry.context.get("permission").unwrap(), "SetCellLeader");
assert_eq!(entry.context.get("cell_id").unwrap(), "cell-1");
}
#[test]
fn test_audit_entry_json_serialization() {
let entry = AuditLogEntry::new(
AuditEventType::Authentication,
"device:abc123",
true,
"Login successful",
42,
);
let json = entry.to_json();
assert!(json.contains("\"entity_id\":\"device:abc123\""));
assert!(json.contains("\"success\":true"));
assert!(json.contains("\"sequence\":42"));
}
#[test]
fn test_memory_audit_logger() {
let logger = MemoryAuditLogger::new();
logger.log_authentication("device:abc", true, Some("verified"));
logger.log_denial("device:abc", "SetLeader", "cell-1", "not authorized");
logger.log_operation("device:abc", "store_cell", "cell-1", true);
assert_eq!(logger.entry_count(), 3);
let auth_entries = logger.entries_by_type(AuditEventType::Authentication);
assert_eq!(auth_entries.len(), 1);
assert!(auth_entries[0].success);
let authz_entries = logger.entries_by_type(AuditEventType::Authorization);
assert_eq!(authz_entries.len(), 1);
assert!(!authz_entries[0].success);
}
#[test]
fn test_memory_logger_all_event_types() {
let logger = MemoryAuditLogger::new();
logger.log_authentication("dev1", true, None);
logger.log_grant("dev1", Permission::JoinCell, "cell-1");
logger.log_denial("dev1", "SetLeader", "cell-1", "not leader");
logger.log_operation("dev1", "read_cell", "cell-1", true);
logger.log_operation("dev1", "store_cell", "cell-1", true);
logger.log_violation("dev2", SecurityViolation::InvalidSignature, "bad sig");
logger.log_cell_event("dev1", "cell-1", "join", true);
logger.log_key_exchange("dev1", "dev2", true);
logger.log_session("user1", "sess-1", "create", true);
logger.log_encryption("dev1", "encrypt_for_cell", "cell-1", true);
assert_eq!(logger.entry_count(), 10);
assert_eq!(
logger.entries_by_type(AuditEventType::Authentication).len(),
1
);
assert_eq!(
logger.entries_by_type(AuditEventType::Authorization).len(),
2
);
assert_eq!(logger.entries_by_type(AuditEventType::DataAccess).len(), 1);
assert_eq!(
logger
.entries_by_type(AuditEventType::DataModification)
.len(),
1
);
assert_eq!(
logger
.entries_by_type(AuditEventType::SecurityViolation)
.len(),
1
);
assert_eq!(
logger.entries_by_type(AuditEventType::CellFormation).len(),
1
);
assert_eq!(logger.entries_by_type(AuditEventType::KeyExchange).len(), 1);
assert_eq!(
logger
.entries_by_type(AuditEventType::SessionManagement)
.len(),
1
);
assert_eq!(logger.entries_by_type(AuditEventType::Encryption).len(), 1);
}
#[test]
fn test_file_audit_logger() {
let temp_dir = tempfile::tempdir().unwrap();
let log_path = temp_dir.path().join("audit.log");
let logger = FileAuditLogger::new(&log_path).unwrap();
logger.log_authentication("device:test", true, Some("test auth"));
logger.log_denial("device:test", "TestPerm", "target", "testing");
logger.flush().unwrap();
let contents = std::fs::read_to_string(&log_path).unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 2);
let entry: AuditLogEntry = serde_json::from_str(lines[0]).unwrap();
assert_eq!(entry.entity_id, "device:test");
assert!(entry.success);
}
#[test]
fn test_null_audit_logger() {
let logger = NullAuditLogger::new();
logger.log_authentication("test", true, None);
logger.log_denial("test", "perm", "target", "reason");
assert_eq!(logger.entry_count(), 0);
assert!(logger.flush().is_ok());
}
#[test]
fn test_security_violation_types() {
let violations = vec![
SecurityViolation::InvalidSignature,
SecurityViolation::ReplayAttack,
SecurityViolation::UnauthorizedAccess,
SecurityViolation::CertificateError,
SecurityViolation::TamperedMessage,
SecurityViolation::RateLimitExceeded,
SecurityViolation::UnknownDevice,
SecurityViolation::ExpiredCredentials,
SecurityViolation::ProtocolViolation,
];
let logger = MemoryAuditLogger::new();
for violation in violations {
logger.log_violation("test", violation, "test details");
}
assert_eq!(logger.entry_count(), 9);
}
#[test]
fn test_audit_entry_sequence_ordering() {
let logger = MemoryAuditLogger::new();
for i in 0..10 {
logger.log_authentication(&format!("dev{}", i), true, None);
}
let entries = logger.entries();
for (i, entry) in entries.iter().enumerate() {
assert_eq!(entry.sequence, (i + 1) as u64);
}
}
#[test]
fn test_event_type_display() {
assert_eq!(
format!("{}", AuditEventType::Authentication),
"AUTHENTICATION"
);
assert_eq!(
format!("{}", AuditEventType::Authorization),
"AUTHORIZATION"
);
assert_eq!(
format!("{}", AuditEventType::SecurityViolation),
"SECURITY_VIOLATION"
);
}
}