backup_suite/security/
audit.rs

1//! 監査ログモジュール
2//!
3//! HMAC-SHA256による改ざん防止機能を備えた監査ログシステムを提供します。
4//!
5//! # 主要機能
6//!
7//! - **イベント記録**: バックアップ、復元、削除などの重要操作をログ記録
8//! - **改ざん防止**: HMAC-SHA256による整合性検証
9//! - **JSON形式**: 構造化ログデータの保存と検索
10//! - **自動ログローテーション**: サイズベースのログファイル管理
11//!
12//! # セキュリティ設計
13//!
14//! このモジュールは以下のセキュリティ原則に従っています:
15//!
16//! 1. **改ざん検知**: すべてのログエントリにHMACを付与
17//! 2. **暗号学的安全性**: SHA256ハッシュ関数の使用
18//! 3. **監査証跡**: 削除不可能なログ記録(append-only)
19//! 4. **時系列保証**: タイムスタンプによる順序保証
20//!
21//! # 使用例
22//!
23//! ```rust,no_run
24//! use backup_suite::security::audit::{AuditLog, AuditEvent, EventType};
25//!
26//! // 監査ログの初期化
27//! let mut audit_log = AuditLog::new()?;
28//!
29//! // バックアップイベントの記録
30//! let event = AuditEvent::backup_started("/path/to/backup", "admin");
31//! audit_log.log(event)?;
32//!
33//! // ログの検証
34//! let is_valid = audit_log.verify_all()?;
35//! assert!(is_valid, "ログが改ざんされています");
36//! # Ok::<(), anyhow::Error>(())
37//! ```
38
39use 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/// 監査イベントの種類
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub enum EventType {
51    /// バックアップ開始
52    BackupStarted,
53    /// バックアップ完了
54    BackupCompleted,
55    /// バックアップ失敗
56    BackupFailed,
57    /// 復元開始
58    RestoreStarted,
59    /// 復元完了
60    RestoreCompleted,
61    /// 復元失敗
62    RestoreFailed,
63    /// クリーンアップ開始
64    CleanupStarted,
65    /// クリーンアップ完了
66    CleanupCompleted,
67    /// クリーンアップ失敗
68    CleanupFailed,
69    /// 設定変更
70    ConfigurationChanged,
71    /// セキュリティ警告
72    SecurityWarning,
73    /// 権限エラー
74    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/// 監査イベント
97///
98/// 監査ログの個別エントリを表します。
99/// 各エントリにはHMAC-SHA256による署名が含まれ、改ざん検知が可能です。
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct AuditEvent {
102    /// イベントのタイムスタンプ(UTC)
103    pub timestamp: DateTime<Utc>,
104    /// イベント種別
105    pub event_type: EventType,
106    /// ユーザー/プロセス識別子
107    pub user: String,
108    /// 対象パス(該当する場合)
109    pub target: Option<String>,
110    /// 追加のメタデータ
111    pub metadata: Option<serde_json::Value>,
112    /// HMAC-SHA256署名(hex文字列)
113    pub hmac: String,
114}
115
116impl AuditEvent {
117    /// バックアップ開始イベントを作成
118    #[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    /// バックアップ完了イベントを作成
129    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    /// バックアップ失敗イベントを作成
143    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    /// 復元開始イベントを作成
158    #[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    /// 復元完了イベントを作成
169    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    /// 復元失敗イベントを作成
183    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    /// クリーンアップ開始イベントを作成
198    #[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    /// クリーンアップ完了イベントを作成
205    #[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    /// クリーンアップ失敗イベントを作成
216    #[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    /// セキュリティ警告イベントを作成
223    #[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    /// 権限拒否イベントを作成
235    #[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    /// 新しい監査イベントを作成(内部使用)
246    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(), // HMACは後でcompute_hmacで計算
259        }
260    }
261
262    /// イベントのペイロード(HMAC計算用)を生成
263    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    /// HMAC-SHA256を計算
278    #[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(); // 機密データを消去
284        result
285    }
286
287    /// HMACを検証
288    #[must_use]
289    pub fn verify_hmac(&self, secret: &[u8]) -> bool {
290        let computed = self.compute_hmac(secret);
291        // タイミング攻撃対策:定数時間比較
292        constant_time_eq(self.hmac.as_bytes(), computed.as_bytes())
293    }
294}
295
296/// 監査ログ管理
297///
298/// 監査イベントの記録、検証、検索機能を提供します。
299///
300/// # セキュリティ特性
301///
302/// - **HMAC-SHA256**: すべてのログエントリに署名
303/// - **append-only**: ログは追記のみ(上書き・削除不可)
304/// - **自動ローテーション**: 10MBを超えるとログファイルをローテーション
305pub struct AuditLog {
306    /// ログファイルのパス
307    log_path: PathBuf,
308    /// HMAC計算用の秘密鍵
309    pub(crate) secret: Vec<u8>,
310    /// 最大ログファイルサイズ(バイト)
311    pub max_log_size: u64,
312}
313
314impl AuditLog {
315    /// デフォルトのログパスを使用して監査ログを初期化
316    ///
317    /// # Errors
318    ///
319    /// * 設定ディレクトリが取得できない場合(`dirs::config_dir()`がNoneを返す)
320    /// * 設定ディレクトリ(`~/.config/backup-suite`等)の作成に失敗した場合
321    /// * 秘密鍵の読み込みまたは生成に失敗した場合
322    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    /// カスタムパスで監査ログを初期化
333    ///
334    /// # Errors
335    ///
336    /// * 秘密鍵ファイルの読み込みに失敗した場合
337    /// * 秘密鍵の新規生成・保存に失敗した場合
338    /// * 秘密鍵ファイルのパーミッション設定に失敗した場合(Unix系)
339    pub fn with_path(log_path: PathBuf) -> Result<Self> {
340        // 秘密鍵の生成または読み込み
341        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, // 10MB
347        })
348    }
349
350    /// 秘密鍵を読み込みまたは生成
351    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            // 既存の秘密鍵を読み込み
356            std::fs::read(&secret_path).context("秘密鍵の読み込みに失敗しました")
357        } else {
358            // 新しい秘密鍵を生成
359            let secret = generate_random_bytes(32); // 256ビット
360            std::fs::write(&secret_path, &secret).context("秘密鍵の保存に失敗しました")?;
361
362            // セキュリティ強化:Unix系でファイル権限を600に設定
363            #[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    /// 監査イベントをログに記録
376    ///
377    /// # Errors
378    ///
379    /// * ログファイルのメタデータ取得に失敗した場合(ローテーションチェック時)
380    /// * ログローテーション(ファイル名変更)に失敗した場合
381    /// * ログファイルのオープンに失敗した場合
382    /// * イベントのJSON形式へのシリアライズに失敗した場合
383    /// * ログファイルへの書き込みに失敗した場合
384    pub fn log(&mut self, mut event: AuditEvent) -> Result<()> {
385        // HMACを計算
386        event.hmac = event.compute_hmac(&self.secret);
387
388        // ログローテーションチェック
389        if self.should_rotate()? {
390            self.rotate_log()?;
391        }
392
393        // JSON形式でログファイルに追記
394        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    /// ログローテーションが必要かチェック
407    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    /// ログファイルをローテーション
417    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    /// すべてのログエントリを読み込み
430    ///
431    /// # Errors
432    ///
433    /// * ログファイルのオープンに失敗した場合
434    /// * ログファイルの行読み込みに失敗した場合
435    /// * ログエントリのJSON形式パースに失敗した場合(ログファイル破損時)
436    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    /// すべてのログエントリのHMACを検証
463    ///
464    /// # Errors
465    ///
466    /// * ログファイルの読み込みに失敗した場合(`read_all()`のエラーを参照)
467    /// * ログエントリのパースに失敗した場合
468    ///
469    /// # 戻り値
470    ///
471    /// * `Ok(true)` - すべてのログエントリのHMAC検証が成功
472    /// * `Ok(false)` - 1つ以上のログエントリのHMAC検証が失敗(ログ改ざん検出)
473    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    /// 特定の期間のログエントリを取得
487    ///
488    /// # Errors
489    ///
490    /// * ログファイルの読み込みに失敗した場合(`read_all()`のエラーを参照)
491    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    /// 特定の種類のイベントを取得
500    ///
501    /// # Errors
502    ///
503    /// * ログファイルの読み込みに失敗した場合(`read_all()`のエラーを参照)
504    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    /// 現在のユーザー名を取得(システム環境変数から)
513    #[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        // 機密データを消去
524        self.secret.zeroize();
525    }
526}
527
528/// HMAC-SHA256の計算
529fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
530    // RFC 2104に基づくHMAC実装
531    const BLOCK_SIZE: usize = 64; // SHA256のブロックサイズ
532    let mut key_padded = vec![0u8; BLOCK_SIZE];
533
534    if key.len() > BLOCK_SIZE {
535        // 鍵が長すぎる場合はハッシュ化
536        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    // ipad (0x36の繰り返し)
543    let mut ipad = key_padded.clone();
544    for byte in &mut ipad {
545        *byte ^= 0x36;
546    }
547
548    // opad (0x5cの繰り返し)
549    let mut opad = key_padded;
550    for byte in &mut opad {
551        *byte ^= 0x5c;
552    }
553
554    // HMAC = H(opad || H(ipad || message))
555    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
566/// 定数時間での文字列比較(タイミング攻撃対策)
567fn 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
579/// 暗号学的に安全な乱数バイト列を生成
580fn 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
587// hexエンコード用の簡易実装
588mod 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        // イベントを改ざん
625        event.user = "attacker".to_string();
626
627        // 改ざん検知
628        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        // イベント記録
639        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        // イベント読み込み
647        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        // イベント記録
663        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        // 検証成功
671        assert!(audit_log.verify_all()?);
672
673        // ログファイルを直接改ざん
674        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        // 検証失敗(改ざん検知)
679        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        // 同じ入力は同じHMACを生成
700        assert_eq!(hmac1, hmac2);
701
702        // 異なる鍵は異なるHMACを生成
703        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        // 異なる種類のイベントを記録
722        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        // 特定種類のイベントのみ取得
731        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; // テスト用に小さい値に設定
745
746        // 多数のイベントを記録(ローテーションをトリガー)
747        for i in 0..50 {
748            audit_log.log(AuditEvent::backup_started(format!("/path/{i}"), "user1"))?;
749        }
750
751        // ローテーションされたファイルが存在することを確認
752        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}