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...\n");
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
99        .map(std::string::String::as_str)
100        .or(temp_map_file.as_deref());
101
102    // Delegate to existing entity_churn implementation
103    let result = crate::commands::entity_churn::run(
104        log_files,
105        map_to_use,
106        baseline.map(std::string::String::as_str),
107        output.map(std::string::String::as_str),
108        format.map(std::string::String::as_str),
109    );
110
111    // Cleanup temp file
112    if let Some(temp) = temp_map_file {
113        let _ = std::fs::remove_file(temp);
114    }
115
116    result
117}
118
119/// Run creation analysis subcommand
120pub fn run_creation(
121    log_files: &[String],
122    entity_map: Option<&String>,
123    output: Option<&String>,
124    auto_preprocess: bool,
125) -> Result<()> {
126    // Auto-preprocessing: build entity map in-memory and write to temp file
127    let temp_map_file = if auto_preprocess && entity_map.is_none() {
128        eprintln!("Auto-preprocessing: Building entity mappings in-memory...\n");
129        let map = crate::commands::preprocess_entities::build_entity_map(log_files)?;
130        let temp_path = write_temp_entity_map(&map)?;
131        eprintln!("Entity mappings ready\n");
132        Some(temp_path)
133    } else {
134        None
135    };
136
137    // Use provided map or auto-generated temp map
138    let map_to_use = entity_map
139        .map(std::string::String::as_str)
140        .or(temp_map_file.as_deref());
141
142    // Delegate to existing entity_creation implementation
143    let result = crate::commands::entity_creation::run(
144        log_files,
145        map_to_use,
146        output.map(std::string::String::as_str),
147    );
148
149    // Cleanup temp file
150    if let Some(temp) = temp_map_file {
151        let _ = std::fs::remove_file(temp);
152    }
153
154    result
155}
156
157/// Run preprocess subcommand
158pub fn run_preprocess(log_files: &[String], output: &str, format: &str) -> Result<()> {
159    // Delegate to existing preprocess_entities implementation
160    crate::commands::preprocess_entities::run(log_files, output, format)
161}
162
163/// Run gaps detection subcommand
164pub fn run_gaps(log_files: &[String], window_seconds: u64) -> Result<()> {
165    // Delegate to existing entity_gaps implementation
166    crate::commands::entity_gaps::run(log_files, window_seconds)
167}
168
169/// Run timeline subcommand
170pub fn run_timeline(
171    log_files: &[String],
172    entity_id: &str,
173    display_name: Option<&String>,
174) -> Result<()> {
175    // Delegate to existing entity_timeline implementation
176    crate::commands::entity_timeline::run(log_files, entity_id, display_name)
177}