use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::sync::Mutex;
use chrono::Local;
pub struct FileLogger {
file: Mutex<std::fs::File>,
}
impl FileLogger {
pub fn new() -> Self {
let path = if let Ok(f) = OpenOptions::new()
.create(true)
.append(true)
.open("/var/log/secure-init.log")
{
f
} else {
let local_path = Path::new("./secure-init.log");
OpenOptions::new()
.create(true)
.append(true)
.open(local_path)
.expect("无法创建日志文件")
};
Self {
file: Mutex::new(path),
}
}
pub fn log(&self, message: &str) {
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
let line = format!("[{}] {}\n", timestamp, message);
if let Ok(mut f) = self.file.lock() {
let _ = writeln!(f, "{}", line.trim());
let _ = f.flush();
}
}
pub fn log_operation(&self, step: &str, detail: &str) {
let sanitized = sanitize_detail(detail);
self.log(&format!("[操作] {}: {}", step, sanitized));
}
}
impl Default for FileLogger {
fn default() -> Self {
Self::new()
}
}
fn sanitize_detail(detail: &str) -> String {
let mut result = detail.to_string();
if let Some(start) = result.find("ssh-ed25519 ") {
if let Some(end) = result[start..]
.find('\n')
.or_else(|| result[start..].find('\r'))
{
let abs_end = start + end;
result.replace_range(start..abs_end, "ssh-ed25519 [KEY_REDACTED]");
} else {
result.replace_range(start.., "ssh-ed25519 [KEY_REDACTED]");
}
}
if let Some(start) = result.find("ssh-rsa ") {
if let Some(end) = result[start..]
.find('\n')
.or_else(|| result[start..].find('\r'))
{
let abs_end = start + end;
result.replace_range(start..abs_end, "ssh-rsa [KEY_REDACTED]");
} else {
result.replace_range(start.., "ssh-rsa [KEY_REDACTED]");
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_ed25519_key() {
let detail = "设置公钥: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@host";
let sanitized = sanitize_detail(detail);
assert!(sanitized.contains("[KEY_REDACTED]"));
assert!(!sanitized.contains("AAAAC3NzaC1lZDI1NTE5AAAAI"));
}
#[test]
fn test_sanitize_rsa_key() {
let detail = "设置公钥: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... user@host";
let sanitized = sanitize_detail(detail);
assert!(sanitized.contains("[KEY_REDACTED]"));
assert!(!sanitized.contains("AAAAB3NzaC1yc2EAAAADAQABAAABAQ"));
}
#[test]
fn test_no_false_positive() {
let detail = "配置完成,SSH 端口已修改为 2222";
let sanitized = sanitize_detail(detail);
assert_eq!(sanitized, detail);
}
}