use crate::security::hsm::error::{AuditEventResult, AuditEventSeverity, AuditEventType, HsmError};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug, warn};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLoggerConfig {
pub enabled: bool,
pub storage_type: AuditStorageType,
pub file_path: Option<String>,
pub db_connection: Option<String>,
pub retention_days: u32,
pub max_events: u32,
pub log_sensitive: bool,
#[serde(default)]
pub additional_metrics: Vec<String>,
}
impl Default for AuditLoggerConfig {
fn default() -> Self {
Self {
enabled: true,
storage_type: AuditStorageType::File,
file_path: Some("./logs/hsm_audit.log".to_string()),
db_connection: None,
retention_days: 90,
max_events: 10000,
log_sensitive: false,
additional_metrics: vec![],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuditStorageType {
Memory,
File,
Database,
}
#[derive(Debug)]
pub struct AuditLogger {
config: AuditLoggerConfig,
storage: Arc<Mutex<Box<dyn AuditStorage + Send + Sync>>>,
operation_tracker: Arc<Mutex<HashMap<String, (DateTime<Utc>, String)>>>,
}
impl AuditLogger {
pub async fn new(config: &AuditLoggerConfig) -> Result<Self, HsmError> {
debug!(
"Creating HSM audit logger with storage type: {:?}",
config.storage_type
);
let storage = create_storage(config).await?;
let logger = Self {
config: config.clone(),
storage: Arc::new(Mutex::new(storage)),
operation_tracker: Arc::new(Mutex::new(HashMap::new())),
};
logger.initialize().await?;
Ok(logger)
}
pub async fn initialize(&self) -> Result<(), HsmError> {
debug!("Initializing HSM audit logger");
let storage = self.storage.lock().await;
storage.initialize().await?;
let event = AuditEvent::new(
AuditEventType::HsmInitialize,
AuditEventResult::Success,
AuditEventSeverity::Info,
);
storage.store_event(event).await?;
if let Err(e) = storage
.cleanup(
self.config.retention_days,
Some(self.config.max_events as usize),
)
.await
{
warn!("Failed to cleanup audit logs: {}", e);
}
debug!("HSM audit logger initialized");
Ok(())
}
pub async fn log_event<T: Serialize>(
&self,
event_type: AuditEventType,
result: AuditEventResult,
severity: AuditEventSeverity,
details: T,
) -> Result<(), HsmError> {
if !self.config.enabled {
return Ok(());
}
let details_value = serde_json::to_value(details)
.map_err(|e| HsmError::SerializationError(e.to_string()))?;
let mut event = AuditEvent::new(event_type, result, severity);
let details_map: HashMap<String, String> =
match serde_json::from_value(details_value.clone()) {
Ok(map) => map,
Err(_) => {
let mut map = HashMap::new();
map.insert(
"data".to_string(),
serde_json::to_string(&details_value).unwrap_or_default(),
);
map
}
};
for (key, value) in details_map {
event = event.with_detail(key, value);
}
let storage = self.storage.lock().await;
storage.store_event(event).await
}
pub async fn log<T: serde::Serialize>(
&self,
event_type: crate::security::hsm::error::AuditEventType,
result: crate::security::hsm::error::AuditEventResult,
severity: crate::security::hsm::error::AuditEventSeverity,
details: T,
) -> Result<(), crate::security::hsm::error::HsmError> {
self.log_event(event_type, result, severity, details).await
}
pub async fn log_event_legacy(
&self,
_event_name: &str,
event: &crate::security::hsm::types::HsmAuditEvent,
) -> Result<(), HsmError> {
if !self.config.enabled {
return Ok(());
}
let event_type = match event.event_type.as_str() {
"health_check" => AuditEventType::HsmInitialize,
"key_generation" => AuditEventType::KeyGeneration,
"encryption" => AuditEventType::Encrypt,
"decryption" => AuditEventType::Decrypt,
"signing" => AuditEventType::Sign,
"verification" => AuditEventType::Verify,
"key_rotation" => AuditEventType::KeyRotation,
"operation" => AuditEventType::OperationRequest,
_ => AuditEventType::Custom(event.event_type.clone()),
};
let result = match event.status.as_str() {
"started" => AuditEventResult::InProgress,
"success" => AuditEventResult::Success,
"failed" => AuditEventResult::Failure,
_ => AuditEventResult::InProgress,
};
let severity = AuditEventSeverity::Info;
let details = serde_json::json!({
"provider": event.provider,
"status": event.status,
"details": event.details,
"operation_id": event.operation_id,
});
self.log_event(event_type, result, severity, details).await
}
pub async fn get_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
limit: Option<usize>,
) -> Result<Vec<AuditEvent>, HsmError> {
if !self.config.enabled {
return Ok(Vec::new());
}
let storage = self.storage.lock().await;
storage.get_events(start_time, end_time, limit).await
}
pub async fn count_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
) -> Result<usize, HsmError> {
if !self.config.enabled {
return Ok(0);
}
let storage = self.storage.lock().await;
storage.count_events(start_time, end_time).await
}
pub async fn cleanup(&self) -> Result<usize, HsmError> {
if !self.config.enabled {
return Ok(0);
}
let storage = self.storage.lock().await;
storage
.cleanup(
self.config.retention_days,
Some(self.config.max_events as usize),
)
.await
}
pub async fn track_operation(&self, operation_id: &str, details: &str) -> Result<(), HsmError> {
if !self.config.enabled {
return Ok(());
}
let mut tracker = self.operation_tracker.lock().await;
tracker.insert(operation_id.to_string(), (Utc::now(), details.to_string()));
Ok(())
}
pub async fn is_operation_tracked(&self, operation_id: &str) -> bool {
if !self.config.enabled {
return false;
}
let tracker = self.operation_tracker.lock().await;
tracker.contains_key(operation_id)
}
pub async fn get_operation_details(
&self,
operation_id: &str,
) -> Option<(DateTime<Utc>, String)> {
if !self.config.enabled {
return None;
}
let tracker = self.operation_tracker.lock().await;
tracker.get(operation_id).cloned()
}
pub async fn remove_operation(&self, operation_id: &str) -> Result<(), HsmError> {
if !self.config.enabled {
return Ok(());
}
let mut tracker = self.operation_tracker.lock().await;
tracker.remove(operation_id);
Ok(())
}
}
async fn create_storage(
config: &AuditLoggerConfig,
) -> Result<Box<dyn AuditStorage + Send + Sync>, HsmError> {
match config.storage_type {
AuditStorageType::Memory => Ok(Box::new(MemoryAuditStorage::new())),
AuditStorageType::File => {
let path = config.file_path.clone().ok_or_else(|| {
HsmError::ConfigError("File path is required for file storage".to_string())
})?;
Ok(Box::new(FileAuditStorage::new(path)))
}
AuditStorageType::Database => {
let conn_string = config.db_connection.clone().ok_or_else(|| {
HsmError::ConfigError(
"Database connection string is required for DB storage".to_string(),
)
})?;
Ok(Box::new(DbAuditStorage::new(conn_string)))
}
}
}
#[async_trait]
pub trait AuditStorage: std::fmt::Debug {
async fn initialize(&self) -> Result<(), HsmError>;
async fn store_event(&self, event: AuditEvent) -> Result<(), HsmError>;
async fn get_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
limit: Option<usize>,
) -> Result<Vec<AuditEvent>, HsmError>;
async fn count_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
) -> Result<usize, HsmError>;
async fn cleanup(
&self,
retention_days: u32,
max_events: Option<usize>,
) -> Result<usize, HsmError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub id: String,
pub timestamp: DateTime<Utc>,
pub event_type: String,
pub result: String,
pub severity: String,
pub actor: Option<String>,
pub operation_id: Option<String>,
pub key_id: Option<String>,
pub error: Option<String>,
pub details: HashMap<String, String>,
pub client_ip: Option<String>,
}
impl AuditEvent {
pub fn new(
event_type: AuditEventType,
result: AuditEventResult,
severity: AuditEventSeverity,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
timestamp: Utc::now(),
event_type: event_type.to_string(),
result: result.to_string(),
severity: severity.to_string(),
actor: None,
operation_id: None,
key_id: None,
error: None,
details: HashMap::new(),
client_ip: None,
}
}
pub fn success(event_type: AuditEventType) -> Self {
Self::new(
event_type,
AuditEventResult::Success,
AuditEventSeverity::Info,
)
}
pub fn failure(event_type: AuditEventType, error: impl Into<String>) -> Self {
let mut event = Self::new(
event_type,
AuditEventResult::Failure,
AuditEventSeverity::Error,
);
event.error = Some(error.into());
event
}
pub fn in_progress(event_type: AuditEventType) -> Self {
Self::new(
event_type,
AuditEventResult::InProgress,
AuditEventSeverity::Info,
)
}
pub fn with_actor(mut self, actor: impl Into<String>) -> Self {
self.actor = Some(actor.into());
self
}
pub fn with_operation_id(mut self, operation_id: impl Into<String>) -> Self {
self.operation_id = Some(operation_id.into());
self
}
pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
self.key_id = Some(key_id.into());
self
}
pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.details.insert(key.into(), value.into());
self
}
pub fn with_client_ip(mut self, client_ip: impl Into<String>) -> Self {
self.client_ip = Some(client_ip.into());
self
}
pub fn to_hsm_audit_event(&self) -> crate::security::hsm::error::HsmAuditEvent {
use crate::security::hsm::error::{
AuditEventResult, AuditEventSeverity, AuditEventType, HsmAuditEvent,
};
let event_type = match self.event_type.as_str() {
"HsmOperation" => AuditEventType::HsmOperation,
"KeyGeneration" => AuditEventType::KeyGeneration,
"Authentication" => AuditEventType::Authentication,
"Authorization" => AuditEventType::Custom("Authorization".to_string()),
"ConfigChange" => AuditEventType::ConfigChange,
_ => AuditEventType::Custom(self.event_type.clone()),
};
let result = match self.result.as_str() {
"Success" => AuditEventResult::Success,
"Failure" => AuditEventResult::Failure,
"Denied" => AuditEventResult::Rejected, "Error" => AuditEventResult::Failure, "Timeout" => AuditEventResult::Timeout,
_ => AuditEventResult::Unknown,
};
let severity = match self.severity.as_str() {
"Critical" => AuditEventSeverity::Critical,
"Error" => AuditEventSeverity::Error,
"Warning" => AuditEventSeverity::Warning,
"Info" => AuditEventSeverity::Info,
"Debug" => AuditEventSeverity::Debug,
_ => AuditEventSeverity::Info,
};
let mut event = HsmAuditEvent::new(event_type, result, severity);
let mut metadata = serde_json::Map::new();
let mut params = serde_json::Map::new();
if let Some(actor) = &self.actor {
event = event.with_user(actor.clone());
}
if let Some(key_id) = &self.key_id {
event = event.with_key(key_id.clone());
}
if let Some(error) = &self.error {
metadata.insert(
"error".to_string(),
serde_json::Value::String(error.clone()),
);
}
for (key, value) in &self.details {
metadata.insert(key.clone(), serde_json::Value::String(value.clone()));
}
if let Some(operation_id) = &self.operation_id {
params.insert(
"operation_id".to_string(),
serde_json::Value::String(operation_id.clone()),
);
}
if !metadata.is_empty() {
event = match event
.clone()
.with_metadata(&serde_json::Value::Object(metadata))
{
Ok(updated_event) => updated_event,
Err(_) => event, };
}
if !params.is_empty() {
event = match event
.clone()
.with_parameters(&serde_json::Value::Object(params))
{
Ok(updated_event) => updated_event,
Err(_) => event, };
}
event
}
}
#[derive(Debug)]
pub struct MemoryAuditStorage {
events: Mutex<Vec<AuditEvent>>,
}
impl MemoryAuditStorage {
pub fn new() -> Self {
Self {
events: Mutex::new(Vec::new()),
}
}
}
#[async_trait]
impl AuditStorage for MemoryAuditStorage {
async fn initialize(&self) -> Result<(), HsmError> {
Ok(())
}
async fn store_event(&self, event: AuditEvent) -> Result<(), HsmError> {
let mut events = self.events.lock().await;
events.push(event);
Ok(())
}
async fn get_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
limit: Option<usize>,
) -> Result<Vec<AuditEvent>, HsmError> {
let events = self.events.lock().await;
let filtered_events: Vec<AuditEvent> = events
.iter()
.filter(|event| {
if let Some(start) = start_time {
if event.timestamp < start {
return false;
}
}
if let Some(end) = end_time {
if event.timestamp > end {
return false;
}
}
true
})
.cloned()
.collect();
let result = if let Some(limit_val) = limit {
filtered_events.into_iter().take(limit_val).collect()
} else {
filtered_events
};
Ok(result)
}
async fn count_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
) -> Result<usize, HsmError> {
let events = self.events.lock().await;
let count = events
.iter()
.filter(|event| {
if let Some(start) = start_time {
if event.timestamp < start {
return false;
}
}
if let Some(end) = end_time {
if event.timestamp > end {
return false;
}
}
true
})
.count();
Ok(count)
}
async fn cleanup(
&self,
retention_days: u32,
max_events: Option<usize>,
) -> Result<usize, HsmError> {
let mut events = self.events.lock().await;
let initial_count = events.len();
let cutoff = Utc::now() - chrono::Duration::days(retention_days as i64);
events.retain(|e| e.timestamp >= cutoff);
if let Some(max) = max_events {
if events.len() > max {
events.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
events.truncate(max);
}
}
let removed = initial_count - events.len();
Ok(removed)
}
}
#[derive(Debug)]
pub struct FileAuditStorage {
file_path: String,
}
impl FileAuditStorage {
pub fn new(file_path: String) -> Self {
Self { file_path }
}
async fn load_events(&self) -> Result<Vec<AuditEvent>, HsmError> {
let path = Path::new(&self.file_path);
if !path.exists() {
return Ok(Vec::new());
}
let contents = fs::read_to_string(path).map_err(|e| {
HsmError::AuditStorageError(format!("Failed to read audit log file: {}", e))
})?;
let mut events = Vec::new();
for line in contents.lines() {
if line.trim().is_empty() {
continue;
}
match serde_json::from_str::<AuditEvent>(line) {
Ok(event) => events.push(event),
Err(e) => {
warn!("Failed to parse audit event: {}", e);
}
}
}
Ok(events)
}
}
#[async_trait]
impl AuditStorage for FileAuditStorage {
async fn initialize(&self) -> Result<(), HsmError> {
let file_path = &self.file_path;
let dir_path = Path::new(file_path).parent();
if let Some(dir) = dir_path {
if !dir.exists() {
fs::create_dir_all(dir).map_err(|e| {
HsmError::AuditStorageError(format!("Failed to create directory: {}", e))
})?;
}
}
if !Path::new(file_path).exists() {
File::create(file_path).map_err(|e| {
HsmError::AuditStorageError(format!("Failed to create audit log file: {}", e))
})?;
}
Ok(())
}
async fn store_event(&self, event: AuditEvent) -> Result<(), HsmError> {
let json = serde_json::to_string(&event)
.map_err(|e| HsmError::SerializationError(e.to_string()))?;
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.file_path)
.map_err(|e| {
HsmError::AuditStorageError(format!("Failed to open audit log file: {}", e))
})?;
writeln!(file, "{}", json).map_err(|e| {
HsmError::AuditStorageError(format!("Failed to write to audit log file: {}", e))
})?;
Ok(())
}
async fn get_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
limit: Option<usize>,
) -> Result<Vec<AuditEvent>, HsmError> {
let mut events = self.load_events().await?;
if let Some(start) = start_time {
events.retain(|e| e.timestamp >= start);
}
if let Some(end) = end_time {
events.retain(|e| e.timestamp <= end);
}
events.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
if let Some(lim) = limit {
events.truncate(lim);
}
Ok(events)
}
async fn count_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
) -> Result<usize, HsmError> {
let events = self.load_events().await?;
let count = events
.iter()
.filter(|e| {
if let Some(start) = start_time {
if e.timestamp < start {
return false;
}
}
if let Some(end) = end_time {
if e.timestamp > end {
return false;
}
}
true
})
.count();
Ok(count)
}
async fn cleanup(
&self,
retention_days: u32,
max_events: Option<usize>,
) -> Result<usize, HsmError> {
let mut events = self.load_events().await?;
let initial_count = events.len();
let cutoff = Utc::now() - chrono::Duration::days(retention_days as i64);
events.retain(|e| e.timestamp >= cutoff);
if let Some(max) = max_events {
if events.len() > max {
events.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
events.truncate(max);
}
}
if events.len() < initial_count {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&self.file_path)
.map_err(|e| {
HsmError::AuditStorageError(format!("Failed to open audit log file: {}", e))
})?;
for event in &events {
let json = serde_json::to_string(event)
.map_err(|e| HsmError::SerializationError(e.to_string()))?;
writeln!(file, "{}", json).map_err(|e| {
HsmError::AuditStorageError(format!("Failed to write to audit log file: {}", e))
})?;
}
}
Ok(initial_count - events.len())
}
}
#[derive(Debug)]
pub struct DbAuditStorage {
memory_storage: MemoryAuditStorage, }
impl DbAuditStorage {
pub fn new(_connection_string: String) -> Self {
Self {
memory_storage: MemoryAuditStorage::new(),
}
}
}
#[async_trait]
impl AuditStorage for DbAuditStorage {
async fn initialize(&self) -> Result<(), HsmError> {
debug!("Initializing database audit storage (using memory fallback)");
self.memory_storage.initialize().await
}
async fn store_event(&self, event: AuditEvent) -> Result<(), HsmError> {
debug!(
"Storing event in database (using memory fallback): {}",
event.id
);
self.memory_storage.store_event(event).await
}
async fn get_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
limit: Option<usize>,
) -> Result<Vec<AuditEvent>, HsmError> {
debug!("Getting events from database (using memory fallback)");
self.memory_storage
.get_events(start_time, end_time, limit)
.await
}
async fn count_events(
&self,
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
) -> Result<usize, HsmError> {
debug!("Counting events in database (using memory fallback)");
self.memory_storage.count_events(start_time, end_time).await
}
async fn cleanup(
&self,
retention_days: u32,
max_events: Option<usize>,
) -> Result<usize, HsmError> {
debug!("Cleaning up database (using memory fallback)");
self.memory_storage
.cleanup(retention_days, max_events)
.await
}
}