1use anyhow::{Context, Result};
40use chrono::{DateTime, Utc};
41use serde::{Deserialize, Serialize};
42use sha2::{Digest, Sha256};
43use std::fs::{File, OpenOptions};
44use std::io::{BufRead, BufReader, Write};
45use std::path::{Path, PathBuf};
46use zeroize::Zeroize;
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub enum EventType {
51 BackupStarted,
53 BackupCompleted,
55 BackupFailed,
57 RestoreStarted,
59 RestoreCompleted,
61 RestoreFailed,
63 CleanupStarted,
65 CleanupCompleted,
67 CleanupFailed,
69 ConfigurationChanged,
71 SecurityWarning,
73 PermissionDenied,
75}
76
77impl std::fmt::Display for EventType {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 EventType::BackupStarted => write!(f, "BACKUP_STARTED"),
81 EventType::BackupCompleted => write!(f, "BACKUP_COMPLETED"),
82 EventType::BackupFailed => write!(f, "BACKUP_FAILED"),
83 EventType::RestoreStarted => write!(f, "RESTORE_STARTED"),
84 EventType::RestoreCompleted => write!(f, "RESTORE_COMPLETED"),
85 EventType::RestoreFailed => write!(f, "RESTORE_FAILED"),
86 EventType::CleanupStarted => write!(f, "CLEANUP_STARTED"),
87 EventType::CleanupCompleted => write!(f, "CLEANUP_COMPLETED"),
88 EventType::CleanupFailed => write!(f, "CLEANUP_FAILED"),
89 EventType::ConfigurationChanged => write!(f, "CONFIGURATION_CHANGED"),
90 EventType::SecurityWarning => write!(f, "SECURITY_WARNING"),
91 EventType::PermissionDenied => write!(f, "PERMISSION_DENIED"),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct AuditEvent {
102 pub timestamp: DateTime<Utc>,
104 pub event_type: EventType,
106 pub user: String,
108 pub target: Option<String>,
110 pub metadata: Option<serde_json::Value>,
112 pub hmac: String,
114}
115
116impl AuditEvent {
117 #[must_use]
119 pub fn backup_started(target: impl Into<String>, user: impl Into<String>) -> Self {
120 Self::new(
121 EventType::BackupStarted,
122 user.into(),
123 Some(target.into()),
124 None,
125 )
126 }
127
128 pub fn backup_completed(
130 target: impl Into<String>,
131 user: impl Into<String>,
132 metadata: serde_json::Value,
133 ) -> Self {
134 Self::new(
135 EventType::BackupCompleted,
136 user.into(),
137 Some(target.into()),
138 Some(metadata),
139 )
140 }
141
142 pub fn backup_failed(
144 target: impl Into<String>,
145 user: impl Into<String>,
146 error: impl Into<String>,
147 ) -> Self {
148 let metadata = serde_json::json!({ "error": error.into() });
149 Self::new(
150 EventType::BackupFailed,
151 user.into(),
152 Some(target.into()),
153 Some(metadata),
154 )
155 }
156
157 #[must_use]
159 pub fn restore_started(target: impl Into<String>, user: impl Into<String>) -> Self {
160 Self::new(
161 EventType::RestoreStarted,
162 user.into(),
163 Some(target.into()),
164 None,
165 )
166 }
167
168 pub fn restore_completed(
170 target: impl Into<String>,
171 user: impl Into<String>,
172 metadata: serde_json::Value,
173 ) -> Self {
174 Self::new(
175 EventType::RestoreCompleted,
176 user.into(),
177 Some(target.into()),
178 Some(metadata),
179 )
180 }
181
182 pub fn restore_failed(
184 target: impl Into<String>,
185 user: impl Into<String>,
186 error: impl Into<String>,
187 ) -> Self {
188 let metadata = serde_json::json!({ "error": error.into() });
189 Self::new(
190 EventType::RestoreFailed,
191 user.into(),
192 Some(target.into()),
193 Some(metadata),
194 )
195 }
196
197 #[must_use]
199 pub fn cleanup_started(user: impl Into<String>, days: u32) -> Self {
200 let metadata = serde_json::json!({ "days": days });
201 Self::new(EventType::CleanupStarted, user.into(), None, Some(metadata))
202 }
203
204 #[must_use]
206 pub fn cleanup_completed(user: impl Into<String>, metadata: serde_json::Value) -> Self {
207 Self::new(
208 EventType::CleanupCompleted,
209 user.into(),
210 None,
211 Some(metadata),
212 )
213 }
214
215 #[must_use]
217 pub fn cleanup_failed(user: impl Into<String>, error: impl Into<String>) -> Self {
218 let metadata = serde_json::json!({ "error": error.into() });
219 Self::new(EventType::CleanupFailed, user.into(), None, Some(metadata))
220 }
221
222 #[must_use]
224 pub fn security_warning(message: impl Into<String>, user: impl Into<String>) -> Self {
225 let metadata = serde_json::json!({ "warning": message.into() });
226 Self::new(
227 EventType::SecurityWarning,
228 user.into(),
229 None,
230 Some(metadata),
231 )
232 }
233
234 #[must_use]
236 pub fn permission_denied(path: impl Into<String>, user: impl Into<String>) -> Self {
237 Self::new(
238 EventType::PermissionDenied,
239 user.into(),
240 Some(path.into()),
241 None,
242 )
243 }
244
245 fn new(
247 event_type: EventType,
248 user: String,
249 target: Option<String>,
250 metadata: Option<serde_json::Value>,
251 ) -> Self {
252 Self {
253 timestamp: Utc::now(),
254 event_type,
255 user,
256 target,
257 metadata,
258 hmac: String::new(), }
260 }
261
262 fn payload(&self) -> String {
264 format!(
265 "{}|{}|{}|{}|{}",
266 self.timestamp.to_rfc3339(),
267 self.event_type,
268 self.user,
269 self.target.as_deref().unwrap_or(""),
270 self.metadata
271 .as_ref()
272 .map(std::string::ToString::to_string)
273 .unwrap_or_default()
274 )
275 }
276
277 #[must_use]
279 pub fn compute_hmac(&self, secret: &[u8]) -> String {
280 let payload = self.payload();
281 let mut mac = hmac_sha256(secret, payload.as_bytes());
282 let result = hex::encode(&mac);
283 mac.zeroize(); result
285 }
286
287 #[must_use]
289 pub fn verify_hmac(&self, secret: &[u8]) -> bool {
290 let computed = self.compute_hmac(secret);
291 constant_time_eq(self.hmac.as_bytes(), computed.as_bytes())
293 }
294}
295
296pub struct AuditLog {
306 log_path: PathBuf,
308 pub(crate) secret: Vec<u8>,
310 pub max_log_size: u64,
312}
313
314impl AuditLog {
315 pub fn new() -> Result<Self> {
323 let config_dir = dirs::config_dir()
324 .context("設定ディレクトリが取得できません")?
325 .join("backup-suite");
326 std::fs::create_dir_all(&config_dir).context("設定ディレクトリの作成に失敗しました")?;
327
328 let log_path = config_dir.join("audit.log");
329 Self::with_path(log_path)
330 }
331
332 pub fn with_path(log_path: PathBuf) -> Result<Self> {
340 let secret = Self::load_or_generate_secret(&log_path)?;
342
343 Ok(Self {
344 log_path,
345 secret,
346 max_log_size: 10 * 1024 * 1024, })
348 }
349
350 fn load_or_generate_secret(log_path: &Path) -> Result<Vec<u8>> {
352 let secret_path = log_path.with_extension("key");
353
354 if secret_path.exists() {
355 std::fs::read(&secret_path).context("秘密鍵の読み込みに失敗しました")
357 } else {
358 let secret = generate_random_bytes(32); std::fs::write(&secret_path, &secret).context("秘密鍵の保存に失敗しました")?;
361
362 #[cfg(unix)]
364 {
365 use std::os::unix::fs::PermissionsExt;
366 let mut perms = std::fs::metadata(&secret_path)?.permissions();
367 perms.set_mode(0o600);
368 std::fs::set_permissions(&secret_path, perms)?;
369 }
370
371 Ok(secret)
372 }
373 }
374
375 pub fn log(&mut self, mut event: AuditEvent) -> Result<()> {
385 event.hmac = event.compute_hmac(&self.secret);
387
388 if self.should_rotate()? {
390 self.rotate_log()?;
391 }
392
393 let mut file = OpenOptions::new()
395 .create(true)
396 .append(true)
397 .open(&self.log_path)
398 .context("ログファイルのオープンに失敗しました")?;
399
400 let json_line = serde_json::to_string(&event)?;
401 writeln!(file, "{json_line}").context("ログの書き込みに失敗しました")?;
402
403 Ok(())
404 }
405
406 fn should_rotate(&self) -> Result<bool> {
408 if !self.log_path.exists() {
409 return Ok(false);
410 }
411
412 let metadata = std::fs::metadata(&self.log_path)?;
413 Ok(metadata.len() > self.max_log_size)
414 }
415
416 fn rotate_log(&self) -> Result<()> {
418 let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
419 let rotated_path = self
420 .log_path
421 .with_file_name(format!("audit_{timestamp}.log"));
422
423 std::fs::rename(&self.log_path, &rotated_path)
424 .context("ログローテーションに失敗しました")?;
425
426 Ok(())
427 }
428
429 pub fn read_all(&self) -> Result<Vec<AuditEvent>> {
437 if !self.log_path.exists() {
438 return Ok(Vec::new());
439 }
440
441 let file = File::open(&self.log_path).context("ログファイルのオープンに失敗しました")?;
442 let reader = BufReader::new(file);
443
444 let mut events = Vec::new();
445 for (line_num, line) in reader.lines().enumerate() {
446 let line = line.context(format!("{}行目の読み込みに失敗しました", line_num + 1))?;
447 if line.trim().is_empty() {
448 continue;
449 }
450
451 let event: AuditEvent = serde_json::from_str(&line).context(format!(
452 "{}行目のJSONパースに失敗しました: {}",
453 line_num + 1,
454 line
455 ))?;
456 events.push(event);
457 }
458
459 Ok(events)
460 }
461
462 pub fn verify_all(&self) -> Result<bool> {
474 let events = self.read_all()?;
475
476 for (i, event) in events.iter().enumerate() {
477 if !event.verify_hmac(&self.secret) {
478 eprintln!("警告: {}行目のログエントリのHMAC検証に失敗しました", i + 1);
479 return Ok(false);
480 }
481 }
482
483 Ok(true)
484 }
485
486 pub fn get_events_since(&self, since: DateTime<Utc>) -> Result<Vec<AuditEvent>> {
492 let all_events = self.read_all()?;
493 Ok(all_events
494 .into_iter()
495 .filter(|e| e.timestamp >= since)
496 .collect())
497 }
498
499 pub fn get_events_by_type(&self, event_type: &EventType) -> Result<Vec<AuditEvent>> {
505 let all_events = self.read_all()?;
506 Ok(all_events
507 .into_iter()
508 .filter(|e| &e.event_type == event_type)
509 .collect())
510 }
511
512 #[must_use]
514 pub fn current_user() -> String {
515 std::env::var("USER")
516 .or_else(|_| std::env::var("USERNAME"))
517 .unwrap_or_else(|_| "unknown".to_string())
518 }
519}
520
521impl Drop for AuditLog {
522 fn drop(&mut self) {
523 self.secret.zeroize();
525 }
526}
527
528fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
530 const BLOCK_SIZE: usize = 64; let mut key_padded = vec![0u8; BLOCK_SIZE];
533
534 if key.len() > BLOCK_SIZE {
535 let hash = Sha256::digest(key);
537 key_padded[..hash.len()].copy_from_slice(&hash);
538 } else {
539 key_padded[..key.len()].copy_from_slice(key);
540 }
541
542 let mut ipad = key_padded.clone();
544 for byte in &mut ipad {
545 *byte ^= 0x36;
546 }
547
548 let mut opad = key_padded;
550 for byte in &mut opad {
551 *byte ^= 0x5c;
552 }
553
554 let mut hasher = Sha256::new();
556 hasher.update(&ipad);
557 hasher.update(data);
558 let inner_hash = hasher.finalize();
559
560 let mut hasher = Sha256::new();
561 hasher.update(&opad);
562 hasher.update(inner_hash);
563 hasher.finalize().to_vec()
564}
565
566fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
568 if a.len() != b.len() {
569 return false;
570 }
571
572 let mut result = 0u8;
573 for (x, y) in a.iter().zip(b.iter()) {
574 result |= x ^ y;
575 }
576 result == 0
577}
578
579fn generate_random_bytes(len: usize) -> Vec<u8> {
581 use rand::RngCore;
582 let mut bytes = vec![0u8; len];
583 rand::rng().fill_bytes(&mut bytes);
584 bytes
585}
586
587mod hex {
589 #[must_use]
590 pub fn encode(data: &[u8]) -> String {
591 data.iter().map(|b| format!("{b:02x}")).collect::<String>()
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use tempfile::TempDir;
599
600 #[test]
601 fn test_audit_event_creation() {
602 let event = AuditEvent::backup_started("/path/to/backup", "testuser");
603 assert_eq!(event.event_type, EventType::BackupStarted);
604 assert_eq!(event.user, "testuser");
605 assert_eq!(event.target, Some("/path/to/backup".to_string()));
606 }
607
608 #[test]
609 fn test_hmac_computation_and_verification() {
610 let secret = b"test_secret_key";
611 let mut event = AuditEvent::backup_started("/path/to/backup", "testuser");
612 event.hmac = event.compute_hmac(secret);
613
614 assert!(event.verify_hmac(secret));
615 assert!(!event.verify_hmac(b"wrong_secret"));
616 }
617
618 #[test]
619 fn test_hmac_tampering_detection() {
620 let secret = b"test_secret_key";
621 let mut event = AuditEvent::backup_started("/path/to/backup", "testuser");
622 event.hmac = event.compute_hmac(secret);
623
624 event.user = "attacker".to_string();
626
627 assert!(!event.verify_hmac(secret));
629 }
630
631 #[test]
632 fn test_audit_log_write_and_read() -> Result<()> {
633 let temp_dir = TempDir::new()?;
634 let log_path = temp_dir.path().join("test_audit.log");
635
636 let mut audit_log = AuditLog::with_path(log_path)?;
637
638 audit_log.log(AuditEvent::backup_started("/path/1", "user1"))?;
640 audit_log.log(AuditEvent::backup_completed(
641 "/path/1",
642 "user1",
643 serde_json::json!({"files": 10}),
644 ))?;
645
646 let events = audit_log.read_all()?;
648 assert_eq!(events.len(), 2);
649 assert_eq!(events[0].event_type, EventType::BackupStarted);
650 assert_eq!(events[1].event_type, EventType::BackupCompleted);
651
652 Ok(())
653 }
654
655 #[test]
656 fn test_audit_log_verification() -> Result<()> {
657 let temp_dir = TempDir::new()?;
658 let log_path = temp_dir.path().join("test_audit.log");
659
660 let mut audit_log = AuditLog::with_path(log_path.clone())?;
661
662 audit_log.log(AuditEvent::backup_started("/path/1", "user1"))?;
664 audit_log.log(AuditEvent::restore_completed(
665 "/path/1",
666 "user1",
667 serde_json::json!({"files": 5}),
668 ))?;
669
670 assert!(audit_log.verify_all()?);
672
673 let mut content = std::fs::read_to_string(&log_path)?;
675 content = content.replace("user1", "attacker");
676 std::fs::write(&log_path, content)?;
677
678 let audit_log = AuditLog::with_path(log_path)?;
680 assert!(!audit_log.verify_all()?);
681
682 Ok(())
683 }
684
685 #[test]
686 fn test_constant_time_eq() {
687 assert!(constant_time_eq(b"hello", b"hello"));
688 assert!(!constant_time_eq(b"hello", b"world"));
689 assert!(!constant_time_eq(b"short", b"longer"));
690 }
691
692 #[test]
693 fn test_hmac_sha256() {
694 let key = b"secret";
695 let data = b"message";
696 let hmac1 = hmac_sha256(key, data);
697 let hmac2 = hmac_sha256(key, data);
698
699 assert_eq!(hmac1, hmac2);
701
702 let hmac3 = hmac_sha256(b"different_secret", data);
704 assert_ne!(hmac1, hmac3);
705 }
706
707 #[test]
708 fn test_event_type_display() {
709 assert_eq!(EventType::BackupStarted.to_string(), "BACKUP_STARTED");
710 assert_eq!(EventType::RestoreCompleted.to_string(), "RESTORE_COMPLETED");
711 assert_eq!(EventType::SecurityWarning.to_string(), "SECURITY_WARNING");
712 }
713
714 #[test]
715 fn test_get_events_by_type() -> Result<()> {
716 let temp_dir = TempDir::new()?;
717 let log_path = temp_dir.path().join("test_audit.log");
718
719 let mut audit_log = AuditLog::with_path(log_path)?;
720
721 audit_log.log(AuditEvent::backup_started("/path/1", "user1"))?;
723 audit_log.log(AuditEvent::restore_started("/path/2", "user1"))?;
724 audit_log.log(AuditEvent::backup_completed(
725 "/path/1",
726 "user1",
727 serde_json::json!({}),
728 ))?;
729
730 let backup_events = audit_log.get_events_by_type(&EventType::BackupStarted)?;
732 assert_eq!(backup_events.len(), 1);
733 assert_eq!(backup_events[0].target, Some("/path/1".to_string()));
734
735 Ok(())
736 }
737
738 #[test]
739 fn test_log_rotation() -> Result<()> {
740 let temp_dir = TempDir::new()?;
741 let log_path = temp_dir.path().join("test_audit.log");
742
743 let mut audit_log = AuditLog::with_path(log_path.clone())?;
744 audit_log.max_log_size = 100; for i in 0..50 {
748 audit_log.log(AuditEvent::backup_started(format!("/path/{i}"), "user1"))?;
749 }
750
751 let entries: Vec<_> = std::fs::read_dir(temp_dir.path())?
753 .filter_map(std::result::Result::ok)
754 .filter(|e| {
755 e.file_name().to_string_lossy().starts_with("audit_")
756 && e.file_name().to_string_lossy().ends_with(".log")
757 })
758 .collect();
759
760 assert!(
761 !entries.is_empty(),
762 "ログローテーションが実行されませんでした"
763 );
764
765 Ok(())
766 }
767}