Files
markbase/markbase-core/src/audit.rs
Warren d94cb2df4c Fix code quality: trailing whitespace, unused imports, clippy warnings
- Fix trailing whitespace in kex.rs and s3.rs
- Add missing KexProposal import in kex_complete.rs
- Auto-fix clippy warnings across all crates
- All 153 tests pass
2026-06-19 05:21:38 +08:00

133 lines
3.3 KiB
Rust

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogEntry {
timestamp: DateTime<Utc>,
operation: String,
config_type: String,
key: String,
old_value: String,
new_value: String,
user: String,
ip_address: Option<String>,
}
pub struct AuditLogger {
log_path: PathBuf,
}
impl AuditLogger {
pub fn new(log_path: &str) -> Self {
Self {
log_path: PathBuf::from(log_path),
}
}
pub fn default() -> Self {
Self::new("logs/config_audit.log")
}
pub fn log_config_change(
&self,
config_type: &str,
key: &str,
old_value: &str,
new_value: &str,
user: &str,
ip_address: Option<&str>,
) -> anyhow::Result<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
operation: "edit".to_string(),
config_type: config_type.to_string(),
key: key.to_string(),
old_value: old_value.to_string(),
new_value: new_value.to_string(),
user: user.to_string(),
ip_address: ip_address.map(|s| s.to_string()),
};
self.write_entry(&entry)?;
log::info!(
"Audit: {} config {} changed from '{}' to '{}' by {}",
config_type,
key,
old_value,
new_value,
user
);
Ok(())
}
pub fn log_config_validate(
&self,
config_type: &str,
result: &str,
user: &str,
ip_address: Option<&str>,
) -> anyhow::Result<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
operation: "validate".to_string(),
config_type: config_type.to_string(),
key: "validation".to_string(),
old_value: "".to_string(),
new_value: result.to_string(),
user: user.to_string(),
ip_address: ip_address.map(|s| s.to_string()),
};
self.write_entry(&entry)?;
Ok(())
}
fn write_entry(&self, entry: &AuditLogEntry) -> anyhow::Result<()> {
// Create logs directory if not exists
if let Some(parent) = self.log_path.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
// Open file in append mode
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.log_path)?;
// Write JSON line
let json = serde_json::to_string(entry)?;
file.write_all(json.as_bytes())?;
file.write_all(b"\n")?;
Ok(())
}
pub fn read_recent_entries(&self, limit: usize) -> anyhow::Result<Vec<AuditLogEntry>> {
if !self.log_path.exists() {
return Ok(Vec::new());
}
let content = std::fs::read_to_string(&self.log_path)?;
let entries: Vec<AuditLogEntry> = content
.lines()
.filter_map(|line| serde_json::from_str(line).ok())
.collect();
// Return last N entries
let start = if entries.len() > limit {
entries.len() - limit
} else {
0
};
Ok(entries[start..].to_vec())
}
}