vault_audit_tools/commands/
k8s_auth.rs1use crate::audit::types::AuditEntry;
33use crate::utils::format::format_number;
34use crate::utils::processor::{ProcessingMode, ProcessorBuilder};
35use anyhow::Result;
36use std::collections::HashMap;
37
38#[derive(Debug, Clone)]
39struct K8sAuthState {
40 k8s_logins: usize,
41 entities_seen: HashMap<String, usize>,
42}
43
44impl K8sAuthState {
45 fn new() -> Self {
46 Self {
47 k8s_logins: 0,
48 entities_seen: HashMap::with_capacity(1000),
49 }
50 }
51
52 fn merge(mut self, other: Self) -> Self {
53 self.k8s_logins += other.k8s_logins;
54 for (entity, count) in other.entities_seen {
55 *self.entities_seen.entry(entity).or_insert(0) += count;
56 }
57 self
58 }
59}
60
61pub fn run(log_files: &[String], output: Option<&str>) -> Result<()> {
62 let processor = ProcessorBuilder::new()
63 .mode(ProcessingMode::Auto)
64 .progress_label("Processing".to_string())
65 .build();
66
67 let (result, stats) = processor.process_files_streaming(
68 log_files,
69 |entry: &AuditEntry, state: &mut K8sAuthState| {
70 if entry.entry_type != "response" || entry.error.is_some() {
72 return;
73 }
74
75 let Some(request) = &entry.request else {
76 return;
77 };
78
79 let path = match &request.path {
80 Some(p) => p.as_str(),
81 None => return,
82 };
83
84 if !path.ends_with("/login") {
85 return;
86 }
87
88 let is_k8s_by_path = path.contains("kubernetes") || path.contains("openshift");
90 let is_k8s_by_mount = request
91 .mount_type
92 .as_deref()
93 .is_some_and(|mt| mt == "kubernetes" || mt == "openshift");
94
95 if is_k8s_by_path || is_k8s_by_mount {
96 state.k8s_logins += 1;
97
98 if let Some(entity_id) = entry.auth.as_ref().and_then(|a| a.entity_id.as_deref()) {
99 *state
100 .entities_seen
101 .entry(entity_id.to_string())
102 .or_insert(0) += 1;
103 }
104 }
105 },
106 K8sAuthState::merge,
107 K8sAuthState::new(),
108 )?;
109
110 let total_lines = stats.total_lines;
111 let k8s_logins = result.k8s_logins;
112 let entities_seen = result.entities_seen;
113
114 eprintln!(
115 "\nTotal: Processed {} lines, found {} K8s logins",
116 format_number(total_lines),
117 format_number(k8s_logins)
118 );
119
120 println!("\n{}", "=".repeat(80));
121 println!("KUBERNETES/OPENSHIFT AUTHENTICATION ANALYSIS");
122 println!("{}", "=".repeat(80));
123
124 println!("\nSummary:");
125 println!(" Total lines processed: {}", format_number(total_lines));
126 println!(
127 " Total K8s/OpenShift logins: {}",
128 format_number(k8s_logins)
129 );
130 println!(" Unique entities: {}", format_number(entities_seen.len()));
131
132 if k8s_logins > 0 {
133 let ratio = k8s_logins as f64 / entities_seen.len() as f64;
134 println!(" Login-to-Entity ratio: {:.2}", ratio);
135
136 println!("\nTop 20 Entities by Login Count:");
137 println!("{}", "-".repeat(80));
138
139 let mut sorted: Vec<_> = entities_seen.iter().collect();
140 sorted.sort_by(|a, b| b.1.cmp(a.1));
141
142 for (i, (entity, count)) in sorted.iter().take(20).enumerate() {
143 println!("{}. {} - {} logins", i + 1, entity, format_number(**count));
144 }
145 }
146
147 if let Some(output_file) = output {
148 use std::fs::File;
149 use std::io::Write;
150 let mut file = File::create(output_file)?;
151 writeln!(file, "entity_id,login_count")?;
152 for (entity, count) in &entities_seen {
153 writeln!(file, "{},{}", entity, count)?;
154 }
155 println!("\nOutput written to: {}", output_file);
156 }
157
158 println!("\n{}", "=".repeat(80));
159
160 Ok(())
161}