use chrono::Utc;
use serde::Serialize;
use std::collections::HashMap;
use tracing::{error, info, warn};
use uuid::Uuid;
use crate::crypto::EncryptionAlgorithm;
use crate::manifest::Manifest;
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum LogLevel {
Info,
Warn,
Error,
Debug,
}
#[derive(Debug, Clone, Serialize)]
pub struct AuditEvent {
pub timestamp: String,
pub event_type: String,
pub package_id: Option<Uuid>,
pub details: HashMap<String, serde_json::Value>,
}
impl AuditEvent {
pub fn new(event_type: impl Into<String>) -> Self {
Self {
timestamp: Utc::now().to_rfc3339(),
event_type: event_type.into(),
package_id: None,
details: HashMap::new(),
}
}
pub fn with_package_id(mut self, id: Uuid) -> Self {
self.package_id = Some(id);
self
}
pub fn with_detail(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
let key = key.into();
let value = serde_json::to_value(value).unwrap_or_default();
self.details.insert(key, value);
self
}
pub fn log(&self) {
let json = serde_json::to_string(self).unwrap_or_default();
match self.event_type.as_str() {
"error" | "decryption_failed" | "verification_failed" | "execution_failed" => {
error!(target: "crablock_audit", "{}", json);
}
"warn" | "cleanup_failed" => {
warn!(target: "crablock_audit", "{}", json);
}
_ => {
info!(target: "crablock_audit", "{}", json);
}
}
}
}
pub fn log_package_load(manifest: &Manifest, key_source: &str, decrypt_mode: &str) {
AuditEvent::new("package_loaded")
.with_package_id(manifest.package_id)
.with_detail("artifact_name", &manifest.artifact_name)
.with_detail(
"encryption_algorithm",
manifest.encryption_algorithm.as_str(),
)
.with_detail("key_source_type", key_source)
.with_detail("decrypt_mode", decrypt_mode)
.with_detail("version", &manifest.version)
.log();
}
pub fn log_verification_result(package_id: Uuid, success: bool, details: &str) {
let event_type = if success {
"verification_success"
} else {
"verification_failed"
};
AuditEvent::new(event_type)
.with_package_id(package_id)
.with_detail("success", success)
.with_detail("details", details)
.log();
}
pub fn log_execution_start(package_id: Uuid, entrypoint: &str, pid: u32) {
AuditEvent::new("execution_start")
.with_package_id(package_id)
.with_detail("entrypoint", entrypoint)
.with_detail("pid", pid)
.log();
}
pub fn log_execution_stop(package_id: Uuid, exit_code: i32) {
AuditEvent::new("execution_stop")
.with_package_id(package_id)
.with_detail("exit_code", exit_code)
.log();
}
pub fn log_decryption(package_id: Uuid, algorithm: EncryptionAlgorithm, mode: &str) {
AuditEvent::new("decryption")
.with_package_id(package_id)
.with_detail("algorithm", algorithm.as_str())
.with_detail("mode", mode)
.log();
}
pub fn log_cleanup(package_id: Uuid, path: &str, success: bool) {
let event = if success {
"cleanup_success"
} else {
"cleanup_failed"
};
AuditEvent::new(event)
.with_package_id(package_id)
.with_detail("path", path)
.with_detail("success", success)
.log();
}
pub fn init_logging(json_format: bool) {
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
if json_format {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().json())
.init();
} else {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer())
.init();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_event_creation() {
let event = AuditEvent::new("test_event")
.with_package_id(Uuid::new_v4())
.with_detail("key", "value");
assert_eq!(event.event_type, "test_event");
assert!(event.package_id.is_some());
assert!(event.details.contains_key("key"));
}
}