1use crate::{Algorithm, KeyState};
4use serde::{Deserialize, Serialize};
5use std::fs::{File, OpenOptions};
6use std::io::{BufWriter, Write};
7use std::path::{Path, PathBuf};
8use std::time::SystemTime;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "event_type")]
13pub enum AuditEvent {
14 KeyCreated {
16 key_id: String,
18 algorithm: Algorithm,
20 version: u32,
22 },
23
24 KeyAccessed {
26 key_id: String,
28 operation: String,
30 },
31
32 KeyRotated {
34 base_id: String,
36 old_version: u32,
38 new_version: u32,
40 },
41
42 KeyStateChanged {
44 key_id: String,
46 old_state: KeyState,
48 new_state: KeyState,
50 },
51
52 KeyDeleted {
54 key_id: String,
56 version: u32,
58 },
59
60 AuthenticationAttempt {
62 success: bool,
64 storage_path: String,
66 },
67
68 EncryptionPerformed {
70 key_id: String,
72 data_size: usize,
74 },
75
76 DecryptionPerformed {
78 key_id: String,
80 success: bool,
82 },
83
84 ConfigurationChanged {
86 setting: String,
88 old_value: String,
90 new_value: String,
92 },
93
94 ErrorOccurred {
96 operation: String,
98 error_type: String,
100 message: String,
102 },
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct AuditLogEntry {
108 pub timestamp: SystemTime,
110
111 #[serde(flatten)]
113 pub event: AuditEvent,
114
115 pub context: Option<String>,
117}
118
119impl AuditLogEntry {
120 pub fn new(event: AuditEvent) -> Self {
122 Self {
123 timestamp: SystemTime::now(),
124 event,
125 context: None,
126 }
127 }
128
129 pub fn with_context<S: Into<String>>(mut self, context: S) -> Self {
131 self.context = Some(context.into());
132 self
133 }
134}
135
136pub trait AuditLogger: Send + Sync {
138 fn log(&mut self, entry: AuditLogEntry) -> crate::Result<()>;
140
141 fn flush(&mut self) -> crate::Result<()>;
143}
144
145pub struct NoOpLogger;
147
148impl AuditLogger for NoOpLogger {
149 fn log(&mut self, _entry: AuditLogEntry) -> crate::Result<()> {
150 Ok(())
151 }
152
153 fn flush(&mut self) -> crate::Result<()> {
154 Ok(())
155 }
156}
157
158pub struct FileAuditLogger {
160 path: PathBuf,
161 writer: BufWriter<File>,
162}
163
164impl FileAuditLogger {
165 pub fn new<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
167 let path = path.as_ref().to_path_buf();
168
169 if let Some(parent) = path.parent() {
171 std::fs::create_dir_all(parent)?;
172 }
173
174 let file = OpenOptions::new().create(true).append(true).open(&path)?;
175
176 let writer = BufWriter::new(file);
177
178 Ok(Self { path, writer })
179 }
180
181 pub fn path(&self) -> &Path {
183 &self.path
184 }
185}
186
187impl AuditLogger for FileAuditLogger {
188 fn log(&mut self, entry: AuditLogEntry) -> crate::Result<()> {
189 let json = serde_json::to_string(&entry).map_err(|e| {
190 crate::Error::storage(
191 "audit_logging",
192 &format!("failed to serialize audit entry: {}", e),
193 )
194 })?;
195
196 writeln!(self.writer, "{}", json)
197 .map_err(|e| crate::Error::storage("", &format!("failed to write audit log: {}", e)))?;
198
199 Ok(())
200 }
201
202 fn flush(&mut self) -> crate::Result<()> {
203 self.writer.flush().map_err(|e| {
204 crate::Error::storage("audit_flush", &format!("failed to flush audit log: {}", e))
205 })
206 }
207}
208
209impl Drop for FileAuditLogger {
210 fn drop(&mut self) {
211 let _ = self.flush();
212 }
213}
214
215#[derive(Default)]
217pub struct MemoryAuditLogger {
218 entries: Vec<AuditLogEntry>,
219}
220
221impl MemoryAuditLogger {
222 pub fn new() -> Self {
224 Self {
225 entries: Vec::new(),
226 }
227 }
228
229 pub fn entries(&self) -> &[AuditLogEntry] {
231 &self.entries
232 }
233
234 pub fn clear(&mut self) {
236 self.entries.clear();
237 }
238
239 pub fn count_event_type(&self, predicate: impl Fn(&AuditEvent) -> bool) -> usize {
241 self.entries.iter().filter(|e| predicate(&e.event)).count()
242 }
243}
244
245impl AuditLogger for MemoryAuditLogger {
246 fn log(&mut self, entry: AuditLogEntry) -> crate::Result<()> {
247 self.entries.push(entry);
248 Ok(())
249 }
250
251 fn flush(&mut self) -> crate::Result<()> {
252 Ok(())
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_audit_event_serialization() {
262 let event = AuditEvent::KeyCreated {
263 key_id: "test-key-123".to_string(),
264 algorithm: Algorithm::ChaCha20Poly1305,
265 version: 1,
266 };
267
268 let entry = AuditLogEntry::new(event).with_context("test context");
269
270 let json = serde_json::to_string(&entry).unwrap();
271 assert!(json.contains("KeyCreated"));
272 assert!(json.contains("test-key-123"));
273 assert!(json.contains("test context"));
274 }
275
276 #[test]
277 fn test_memory_logger() {
278 let mut logger = MemoryAuditLogger::new();
279
280 let event1 = AuditEvent::KeyCreated {
281 key_id: "key1".to_string(),
282 algorithm: Algorithm::Aes256Gcm,
283 version: 1,
284 };
285
286 let event2 = AuditEvent::KeyAccessed {
287 key_id: "key1".to_string(),
288 operation: "encrypt".to_string(),
289 };
290
291 logger.log(AuditLogEntry::new(event1)).unwrap();
292 logger.log(AuditLogEntry::new(event2)).unwrap();
293
294 assert_eq!(logger.entries().len(), 2);
295
296 let created_count = logger.count_event_type(|e| matches!(e, AuditEvent::KeyCreated { .. }));
297 assert_eq!(created_count, 1);
298 }
299
300 #[test]
301 fn test_file_logger() {
302 use tempfile::tempdir;
303
304 let temp_dir = tempdir().unwrap();
305 let log_path = temp_dir.path().join("audit.log");
306
307 let mut logger = FileAuditLogger::new(&log_path).unwrap();
308
309 let event = AuditEvent::KeyRotated {
310 base_id: "base-123".to_string(),
311 old_version: 1,
312 new_version: 2,
313 };
314
315 logger.log(AuditLogEntry::new(event)).unwrap();
316 logger.flush().unwrap();
317
318 assert!(log_path.exists());
320 let contents = std::fs::read_to_string(&log_path).unwrap();
321 assert!(contents.contains("KeyRotated"));
322 assert!(contents.contains("base-123"));
323 }
324}