vault_audit_tools/commands/
entity_analysis.rs

1//! Unified entity analysis command.
2//!
3//! Consolidates entity lifecycle tracking, creation analysis, preprocessing,
4//! gap detection, and timeline analysis into a single powerful command with
5//! intelligent auto-preprocessing to eliminate multi-step workflows.
6//!
7//! # Usage
8//!
9//! ```bash
10//! # Churn analysis (auto-preprocesses entity mappings)
11//! vault-audit entity-analysis churn logs/day1.log logs/day2.log
12//! vault-audit entity-analysis churn logs/*.log --baseline entities.json
13//!
14//! # Creation analysis by auth path
15//! vault-audit entity-analysis creation logs/*.log
16//! vault-audit entity-analysis creation logs/*.log --export creation_data.json
17//!
18//! # Extract entity mappings (preprocessing)
19//! vault-audit entity-analysis preprocess logs/*.log --output mappings.json
20//! vault-audit entity-analysis preprocess logs/*.log --format csv
21//!
22//! # Detect activity gaps for entities
23//! vault-audit entity-analysis gaps logs/*.log --window-seconds 300
24//!
25//! # Individual entity timeline
26//! vault-audit entity-analysis timeline logs/*.log --entity-id abc-123
27//! ```
28//!
29//! **Key Improvement**: Auto-preprocessing eliminates the need for separate
30//! preprocessing steps. Entity mappings are built in-memory automatically when
31//! needed by churn or creation analysis.
32//!
33//! # Subcommands
34//!
35//! ## churn
36//! Multi-day entity lifecycle tracking with ephemeral pattern detection.
37//! Automatically preprocesses entity mappings unless `--no-auto-preprocess` is specified.
38//!
39//! ## creation
40//! Analyzes when entities were first created, grouped by authentication path.
41//! Shows new entity onboarding patterns and growth trends.
42//!
43//! ## preprocess
44//! Extracts entity-to-display-name mappings from audit logs for external use.
45//! Outputs JSON or CSV format for integration with other tools.
46//!
47//! ## gaps
48//! Detects entities with suspicious activity gaps (potential compromised credentials
49//! or entities that should have been cleaned up).
50//!
51//! ## timeline
52//! Shows chronological activity for a specific entity ID, useful for debugging
53//! or investigating specific identity issues.
54
55use anyhow::Result;
56use std::fs::File;
57use std::io::Write;
58
59/// Helper to write entity map to temp JSON file for commands that expect file paths
60fn write_temp_entity_map(
61    entity_map: &std::collections::HashMap<
62        String,
63        crate::commands::preprocess_entities::EntityMapping,
64    >,
65) -> Result<String> {
66    let temp_path = format!(".vault-audit-autopreprocess-{}.json", std::process::id());
67
68    let file = File::create(&temp_path)?;
69    let mut writer = std::io::BufWriter::new(file);
70    let json = serde_json::to_string_pretty(&entity_map)?;
71    writer.write_all(json.as_bytes())?;
72    writer.flush()?;
73
74    Ok(temp_path)
75}
76
77/// Run churn analysis subcommand
78pub fn run_churn(
79    log_files: &[String],
80    entity_map: Option<&String>,
81    baseline: Option<&String>,
82    output: Option<&String>,
83    format: Option<&String>,
84    auto_preprocess: bool,
85) -> Result<()> {
86    // Auto-preprocessing: build entity map in-memory and write to temp file
87    let temp_map_file = if auto_preprocess && entity_map.is_none() {
88        eprintln!("Auto-preprocessing: Building entity mappings in-memory...");
89        let map = crate::commands::preprocess_entities::build_entity_map(log_files)?;
90        let temp_path = write_temp_entity_map(&map)?;
91        eprintln!("Entity mappings ready\n");
92        Some(temp_path)
93    } else {
94        None
95    };
96
97    // Use provided map or auto-generated temp map
98    let map_to_use = entity_map.map(|s| s.as_str()).or(temp_map_file.as_deref());
99
100    // Delegate to existing entity_churn implementation
101    let result = crate::commands::entity_churn::run(
102        log_files,
103        map_to_use,
104        baseline.map(|s| s.as_str()),
105        output.map(|s| s.as_str()),
106        format.map(|s| s.as_str()),
107    );
108
109    // Cleanup temp file
110    if let Some(temp) = temp_map_file {
111        let _ = std::fs::remove_file(temp);
112    }
113
114    result
115}
116
117/// Run creation analysis subcommand
118pub fn run_creation(
119    log_files: &[String],
120    entity_map: Option<&String>,
121    output: Option<&String>,
122    auto_preprocess: bool,
123) -> Result<()> {
124    // Auto-preprocessing: build entity map in-memory and write to temp file
125    let temp_map_file = if auto_preprocess && entity_map.is_none() {
126        eprintln!("Auto-preprocessing: Building entity mappings in-memory...");
127        let map = crate::commands::preprocess_entities::build_entity_map(log_files)?;
128        let temp_path = write_temp_entity_map(&map)?;
129        eprintln!("Entity mappings ready\n");
130        Some(temp_path)
131    } else {
132        None
133    };
134
135    // Use provided map or auto-generated temp map
136    let map_to_use = entity_map.map(|s| s.as_str()).or(temp_map_file.as_deref());
137
138    // Delegate to existing entity_creation implementation
139    let result =
140        crate::commands::entity_creation::run(log_files, map_to_use, output.map(|s| s.as_str()));
141
142    // Cleanup temp file
143    if let Some(temp) = temp_map_file {
144        let _ = std::fs::remove_file(temp);
145    }
146
147    result
148}
149
150/// Run preprocess subcommand
151pub fn run_preprocess(log_files: &[String], output: &str, format: &str) -> Result<()> {
152    // Delegate to existing preprocess_entities implementation
153    crate::commands::preprocess_entities::run(log_files, output, format)
154}
155
156/// Run gaps detection subcommand
157pub fn run_gaps(log_files: &[String], window_seconds: u64) -> Result<()> {
158    // Delegate to existing entity_gaps implementation
159    crate::commands::entity_gaps::run(log_files, window_seconds)
160}
161
162/// Run timeline subcommand
163pub fn run_timeline(
164    log_files: &[String],
165    entity_id: &str,
166    display_name: Option<&String>,
167) -> Result<()> {
168    // Convert Option<&String> to &Option<String> for compatibility
169    let display_name_owned = display_name.cloned();
170    // Delegate to existing entity_timeline implementation
171    crate::commands::entity_timeline::run(log_files, entity_id, &display_name_owned)
172}