1use crate::{
7 AuthConfig, AuthenticationManager, ValidationConfig,
8 config::StorageConfig,
9 manager::AuthError,
10 vault::{VaultConfig, VaultError, VaultIntegration},
11};
12use std::collections::HashMap;
13use tracing::{debug, info, warn};
14
15pub struct VaultAuthenticationManager {
17 auth_manager: AuthenticationManager,
18 vault_integration: Option<VaultIntegration>,
19 fallback_to_env: bool,
20}
21
22impl VaultAuthenticationManager {
23 pub async fn new_with_vault(
25 mut auth_config: AuthConfig,
26 validation_config: Option<ValidationConfig>,
27 vault_config: Option<VaultConfig>,
28 fallback_to_env: bool,
29 ) -> Result<Self, VaultAuthManagerError> {
30 let vault_integration = if let Some(vault_cfg) = vault_config {
31 match VaultIntegration::new(vault_cfg).await {
32 Ok(integration) => {
33 info!(
34 "Successfully connected to vault: {}",
35 integration.client_info().name
36 );
37 Some(integration)
38 }
39 Err(e) => {
40 if fallback_to_env {
41 warn!(
42 "Failed to connect to vault ({}), falling back to environment variables",
43 e
44 );
45 None
46 } else {
47 return Err(VaultAuthManagerError::VaultError(e));
48 }
49 }
50 }
51 } else {
52 None
53 };
54
55 let master_key = if let Some(vault) = &vault_integration {
57 match vault.get_master_key().await {
58 Ok(key) => {
59 debug!("Retrieved master key from vault");
60 key
61 }
62 Err(VaultError::SecretNotFound(_)) => {
63 if fallback_to_env {
64 debug!("Master key not found in vault, checking environment");
65 Self::get_master_key_from_env()?
66 } else {
67 return Err(VaultAuthManagerError::MasterKeyNotFound);
68 }
69 }
70 Err(e) => {
71 if fallback_to_env {
72 warn!(
73 "Failed to get master key from vault ({}), checking environment",
74 e
75 );
76 Self::get_master_key_from_env()?
77 } else {
78 return Err(VaultAuthManagerError::VaultError(e));
79 }
80 }
81 }
82 } else {
83 Self::get_master_key_from_env()?
84 };
85
86 unsafe {
89 std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", &master_key);
90 }
91
92 if let Some(vault) = &vault_integration {
94 if let Ok(vault_config) = vault.get_api_config().await {
95 Self::apply_vault_config(&mut auth_config, &vault_config);
96 }
97 }
98
99 let validation_config = validation_config.unwrap_or_default();
101
102 let auth_manager =
104 AuthenticationManager::new_with_validation(auth_config, validation_config)
105 .await
106 .map_err(VaultAuthManagerError::AuthError)?;
107
108 Ok(Self {
109 auth_manager,
110 vault_integration,
111 fallback_to_env,
112 })
113 }
114
115 pub async fn new_with_default_vault(
117 auth_config: AuthConfig,
118 fallback_to_env: bool,
119 ) -> Result<Self, VaultAuthManagerError> {
120 let vault_config = Some(VaultConfig::default());
121 Self::new_with_vault(auth_config, None, vault_config, fallback_to_env).await
122 }
123
124 fn get_master_key_from_env() -> Result<String, VaultAuthManagerError> {
126 std::env::var("PULSEENGINE_MCP_MASTER_KEY")
127 .map_err(|_| VaultAuthManagerError::MasterKeyNotFound)
128 }
129
130 fn apply_vault_config(auth_config: &mut AuthConfig, vault_config: &HashMap<String, String>) {
132 if let Some(timeout) = vault_config.get("PULSEENGINE_MCP_SESSION_TIMEOUT") {
133 if let Ok(timeout_secs) = timeout.parse::<u64>() {
134 auth_config.session_timeout_secs = timeout_secs;
135 debug!(
136 "Applied vault config: session_timeout_secs = {}",
137 timeout_secs
138 );
139 }
140 }
141
142 if let Some(max_attempts) = vault_config.get("PULSEENGINE_MCP_MAX_FAILED_ATTEMPTS") {
143 if let Ok(attempts) = max_attempts.parse::<u32>() {
144 auth_config.max_failed_attempts = attempts;
145 debug!("Applied vault config: max_failed_attempts = {}", attempts);
146 }
147 }
148
149 if let Some(rate_limit) = vault_config.get("PULSEENGINE_MCP_RATE_LIMIT_WINDOW") {
150 if let Ok(window_secs) = rate_limit.parse::<u64>() {
151 auth_config.rate_limit_window_secs = window_secs;
152 debug!(
153 "Applied vault config: rate_limit_window_secs = {}",
154 window_secs
155 );
156 }
157 }
158
159 if let Some(storage_path) = vault_config.get("PULSEENGINE_MCP_STORAGE_PATH") {
160 auth_config.storage = StorageConfig::File {
161 path: storage_path.into(),
162 file_permissions: 0o600,
163 dir_permissions: 0o700,
164 require_secure_filesystem: true,
165 enable_filesystem_monitoring: false,
166 };
167 debug!("Applied vault config: storage_path = {}", storage_path);
168 }
169 }
170
171 pub fn auth_manager(&self) -> &AuthenticationManager {
173 &self.auth_manager
174 }
175
176 pub fn vault_integration(&self) -> Option<&VaultIntegration> {
178 self.vault_integration.as_ref()
179 }
180
181 pub async fn test_vault_connection(&self) -> Result<(), VaultAuthManagerError> {
183 if let Some(vault) = &self.vault_integration {
184 vault
185 .test_connection()
186 .await
187 .map_err(VaultAuthManagerError::VaultError)
188 } else {
189 Err(VaultAuthManagerError::VaultNotConfigured)
190 }
191 }
192
193 pub async fn refresh_config_from_vault(&mut self) -> Result<(), VaultAuthManagerError> {
195 if let Some(vault) = &self.vault_integration {
196 vault.clear_cache().await;
198
199 let vault_config = vault
201 .get_api_config()
202 .await
203 .map_err(VaultAuthManagerError::VaultError)?;
204
205 info!(
206 "Refreshed {} configuration values from vault",
207 vault_config.len()
208 );
209
210 warn!("Configuration refresh requires recreating the authentication manager");
214
215 Ok(())
216 } else {
217 Err(VaultAuthManagerError::VaultNotConfigured)
218 }
219 }
220
221 pub async fn store_secret(&self, name: &str, value: &str) -> Result<(), VaultAuthManagerError> {
223 if let Some(vault) = &self.vault_integration {
224 if let Some(client) = vault.vault_integration() {
225 client
226 .set_secret(name, value)
227 .await
228 .map_err(VaultAuthManagerError::VaultError)
229 } else {
230 Err(VaultAuthManagerError::VaultNotConfigured)
231 }
232 } else {
233 Err(VaultAuthManagerError::VaultNotConfigured)
234 }
235 }
236
237 pub async fn get_secret(&self, name: &str) -> Result<String, VaultAuthManagerError> {
239 if let Some(vault) = &self.vault_integration {
240 vault
241 .get_secret_cached(name)
242 .await
243 .map_err(VaultAuthManagerError::VaultError)
244 } else {
245 Err(VaultAuthManagerError::VaultNotConfigured)
246 }
247 }
248
249 pub async fn list_vault_secrets(&self) -> Result<Vec<String>, VaultAuthManagerError> {
251 if let Some(vault) = &self.vault_integration {
252 if let Some(client) = vault.vault_integration() {
253 client
254 .list_secrets()
255 .await
256 .map_err(VaultAuthManagerError::VaultError)
257 } else {
258 Err(VaultAuthManagerError::VaultNotConfigured)
259 }
260 } else {
261 Err(VaultAuthManagerError::VaultNotConfigured)
262 }
263 }
264
265 pub fn vault_status(&self) -> VaultStatus {
267 if let Some(vault) = &self.vault_integration {
268 VaultStatus {
269 enabled: true,
270 connected: true, client_info: Some(vault.client_info()),
272 fallback_enabled: self.fallback_to_env,
273 }
274 } else {
275 VaultStatus {
276 enabled: false,
277 connected: false,
278 client_info: None,
279 fallback_enabled: self.fallback_to_env,
280 }
281 }
282 }
283}
284
285impl std::ops::Deref for VaultAuthenticationManager {
287 type Target = AuthenticationManager;
288
289 fn deref(&self) -> &Self::Target {
290 &self.auth_manager
291 }
292}
293
294#[derive(Debug, thiserror::Error)]
296pub enum VaultAuthManagerError {
297 #[error("Vault error: {0}")]
298 VaultError(VaultError),
299
300 #[error("Authentication manager error: {0}")]
301 AuthError(AuthError),
302
303 #[error("Master key not found in vault or environment")]
304 MasterKeyNotFound,
305
306 #[error("Vault is not configured")]
307 VaultNotConfigured,
308
309 #[error("Configuration error: {0}")]
310 ConfigError(String),
311}
312
313#[derive(Debug, Clone)]
315pub struct VaultStatus {
316 pub enabled: bool,
317 pub connected: bool,
318 pub client_info: Option<crate::vault::VaultClientInfo>,
319 pub fallback_enabled: bool,
320}
321
322impl std::fmt::Display for VaultStatus {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 writeln!(f, "Vault Status:")?;
325 writeln!(f, " Enabled: {}", self.enabled)?;
326 writeln!(f, " Connected: {}", self.connected)?;
327 writeln!(f, " Fallback Enabled: {}", self.fallback_enabled)?;
328
329 if let Some(info) = &self.client_info {
330 writeln!(f, " Client: {} v{}", info.name, info.version)?;
331 writeln!(f, " Type: {}", info.vault_type)?;
332 writeln!(f, " Read Only: {}", info.read_only)?;
333 }
334
335 Ok(())
336 }
337}
338
339impl VaultIntegration {
341 pub fn vault_integration(&self) -> Option<&dyn crate::vault::VaultClient> {
343 None
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::config::StorageConfig;
353
354 #[test]
355 fn test_vault_status_display() {
356 let status = VaultStatus {
357 enabled: true,
358 connected: true,
359 client_info: Some(crate::vault::VaultClientInfo {
360 name: "Test Vault".to_string(),
361 version: "1.0.0".to_string(),
362 vault_type: crate::vault::VaultType::Infisical,
363 read_only: false,
364 }),
365 fallback_enabled: true,
366 };
367
368 let output = status.to_string();
369 assert!(output.contains("Enabled: true"));
370 assert!(output.contains("Connected: true"));
371 assert!(output.contains("Test Vault"));
372 }
373
374 #[test]
375 fn test_apply_vault_config() {
376 let mut auth_config = AuthConfig {
377 enabled: true,
378 storage: StorageConfig::File {
379 path: std::env::temp_dir()
380 .join("mcp-auth-vault-test")
381 .join("test_vault"),
382 file_permissions: 0o600,
383 dir_permissions: 0o700,
384 require_secure_filesystem: false,
385 enable_filesystem_monitoring: false,
386 },
387 cache_size: 100,
388 session_timeout_secs: 3600,
389 max_failed_attempts: 5,
390 rate_limit_window_secs: 900,
391 };
392
393 let mut vault_config = HashMap::new();
394 vault_config.insert(
395 "PULSEENGINE_MCP_SESSION_TIMEOUT".to_string(),
396 "7200".to_string(),
397 );
398 vault_config.insert(
399 "PULSEENGINE_MCP_MAX_FAILED_ATTEMPTS".to_string(),
400 "3".to_string(),
401 );
402
403 VaultAuthenticationManager::apply_vault_config(&mut auth_config, &vault_config);
404
405 assert_eq!(auth_config.session_timeout_secs, 7200);
406 assert_eq!(auth_config.max_failed_attempts, 3);
407 }
408}