1use crate::security::{Result, SecretsConfig, SecurityError};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tracing::{debug, info, warn};
8
9pub struct SecretsManager {
11 config: SecretsConfig,
12 secrets_cache: Arc<RwLock<HashMap<String, SecretValue>>>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SecretValue {
18 pub value: String,
19 pub version: u32,
20 pub created_at: chrono::DateTime<chrono::Utc>,
21 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
22 pub metadata: HashMap<String, String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SecretMetadata {
28 pub key: String,
29 pub version: u32,
30 pub created_at: chrono::DateTime<chrono::Utc>,
31 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
32 pub metadata: HashMap<String, String>,
33}
34
35impl SecretsManager {
36 pub fn new(config: SecretsConfig) -> Result<Self> {
37 let manager = Self {
38 config,
39 secrets_cache: Arc::new(RwLock::new(HashMap::new())),
40 };
41
42 Ok(manager)
43 }
44
45 pub async fn initialize(&self) -> Result<()> {
47 info!("Initializing secrets management system");
48
49 if self.config.vault_enabled {
50 self.initialize_vault().await?;
51 } else if self.config.env_fallback {
52 self.load_from_environment().await?;
53 }
54
55 info!("Secrets management system initialized");
56 Ok(())
57 }
58
59 async fn initialize_vault(&self) -> Result<()> {
61 if let Some(vault_address) = &self.config.vault_address {
62 info!("Connecting to Vault at: {}", vault_address);
63
64 if let Some(token_path) = &self.config.vault_token_path {
71 if !token_path.exists() {
72 return Err(SecurityError::SecretsError {
73 message: format!("Vault token file not found: {token_path:?}"),
74 });
75 }
76
77 debug!("Vault token found at: {:?}", token_path);
78
79 let _token =
81 fs::read_to_string(token_path).map_err(|e| SecurityError::SecretsError {
82 message: format!("Failed to read Vault token: {e}"),
83 })?;
84
85 info!("Connected to Vault successfully");
86 } else {
87 warn!("Vault enabled but no token path configured");
88 }
89 } else {
90 return Err(SecurityError::SecretsError {
91 message: "Vault enabled but no address configured".to_string(),
92 });
93 }
94
95 Ok(())
96 }
97
98 async fn load_from_environment(&self) -> Result<()> {
100 debug!("Loading secrets from environment variables");
101
102 let mut cache = self.secrets_cache.write().await;
103
104 let env_secrets = vec![
106 "DATABASE_URL",
107 "OPENAI_API_KEY",
108 "JWT_SECRET",
109 "ENCRYPTION_KEY",
110 "VAULT_TOKEN",
111 "API_KEY",
112 "SESSION_SECRET",
113 ];
114
115 for secret_name in env_secrets {
116 if let Ok(secret_value) = std::env::var(secret_name) {
117 let secret = SecretValue {
118 value: secret_value,
119 version: 1,
120 created_at: chrono::Utc::now(),
121 expires_at: None,
122 metadata: HashMap::new(),
123 };
124
125 cache.insert(secret_name.to_string(), secret);
126 debug!("Loaded secret from environment: {}", secret_name);
127 }
128 }
129
130 info!("Loaded {} secrets from environment", cache.len());
131 Ok(())
132 }
133
134 pub async fn get_secret(&self, key: &str) -> Result<String> {
136 {
138 let cache = self.secrets_cache.read().await;
139 if let Some(secret) = cache.get(key) {
140 if let Some(expires_at) = secret.expires_at {
142 if chrono::Utc::now() > expires_at {
143 warn!("Secret '{}' has expired", key);
144 return Err(SecurityError::SecretsError {
145 message: format!("Secret '{key}' has expired"),
146 });
147 }
148 }
149
150 debug!("Retrieved secret from cache: {}", key);
151 return Ok(secret.value.clone());
152 }
153 }
154
155 if self.config.vault_enabled {
157 self.get_secret_from_vault(key).await
158 } else if self.config.env_fallback {
159 self.get_secret_from_env(key).await
160 } else {
161 Err(SecurityError::SecretsError {
162 message: format!("Secret '{key}' not found and no fallback configured"),
163 })
164 }
165 }
166
167 async fn get_secret_from_vault(&self, key: &str) -> Result<String> {
169 Err(SecurityError::SecretsError {
172 message: format!("Vault integration not fully implemented for key: {key}"),
173 })
174 }
175
176 async fn get_secret_from_env(&self, key: &str) -> Result<String> {
178 match std::env::var(key) {
179 Ok(value) => {
180 let secret = SecretValue {
182 value: value.clone(),
183 version: 1,
184 created_at: chrono::Utc::now(),
185 expires_at: None,
186 metadata: HashMap::new(),
187 };
188
189 let mut cache = self.secrets_cache.write().await;
190 cache.insert(key.to_string(), secret);
191
192 debug!("Retrieved secret from environment: {}", key);
193 Ok(value)
194 }
195 Err(_) => Err(SecurityError::SecretsError {
196 message: format!("Secret '{key}' not found in environment"),
197 }),
198 }
199 }
200
201 pub async fn set_secret(
203 &self,
204 key: &str,
205 value: &str,
206 expires_in_seconds: Option<i64>,
207 ) -> Result<()> {
208 let expires_at = expires_in_seconds
209 .map(|seconds| chrono::Utc::now() + chrono::Duration::seconds(seconds));
210
211 let secret = SecretValue {
212 value: value.to_string(),
213 version: 1,
214 created_at: chrono::Utc::now(),
215 expires_at,
216 metadata: HashMap::new(),
217 };
218
219 let mut cache = self.secrets_cache.write().await;
220 cache.insert(key.to_string(), secret);
221
222 debug!("Set secret: {}", key);
223 Ok(())
224 }
225
226 pub async fn delete_secret(&self, key: &str) -> Result<()> {
228 let mut cache = self.secrets_cache.write().await;
229
230 if cache.remove(key).is_some() {
231 debug!("Deleted secret from cache: {}", key);
232 Ok(())
233 } else {
234 Err(SecurityError::SecretsError {
235 message: format!("Secret '{key}' not found in cache"),
236 })
237 }
238 }
239
240 pub async fn get_secret_metadata(&self, key: &str) -> Result<SecretMetadata> {
242 let cache = self.secrets_cache.read().await;
243
244 if let Some(secret) = cache.get(key) {
245 Ok(SecretMetadata {
246 key: key.to_string(),
247 version: secret.version,
248 created_at: secret.created_at,
249 expires_at: secret.expires_at,
250 metadata: secret.metadata.clone(),
251 })
252 } else {
253 Err(SecurityError::SecretsError {
254 message: format!("Secret '{key}' not found"),
255 })
256 }
257 }
258
259 pub async fn list_secrets(&self) -> Vec<String> {
261 let cache = self.secrets_cache.read().await;
262 cache.keys().cloned().collect()
263 }
264
265 pub async fn rotate_secret(&self, key: &str, new_value: &str) -> Result<u32> {
267 let mut cache = self.secrets_cache.write().await;
268
269 let new_version = if let Some(existing_secret) = cache.get(key) {
270 existing_secret.version + 1
271 } else {
272 1
273 };
274
275 let secret = SecretValue {
276 value: new_value.to_string(),
277 version: new_version,
278 created_at: chrono::Utc::now(),
279 expires_at: None,
280 metadata: HashMap::new(),
281 };
282
283 cache.insert(key.to_string(), secret);
284
285 info!("Rotated secret '{}' to version {}", key, new_version);
286 Ok(new_version)
287 }
288
289 pub async fn cleanup_expired_secrets(&self) -> Result<usize> {
291 let mut cache = self.secrets_cache.write().await;
292 let now = chrono::Utc::now();
293
294 let initial_count = cache.len();
295
296 cache.retain(|key, secret| {
297 if let Some(expires_at) = secret.expires_at {
298 if now > expires_at {
299 debug!("Removing expired secret: {}", key);
300 return false;
301 }
302 }
303 true
304 });
305
306 let removed_count = initial_count - cache.len();
307
308 if removed_count > 0 {
309 info!("Cleaned up {} expired secrets", removed_count);
310 }
311
312 Ok(removed_count)
313 }
314
315 pub async fn get_statistics(&self) -> SecretsStatistics {
317 let cache = self.secrets_cache.read().await;
318 let now = chrono::Utc::now();
319
320 let total_secrets = cache.len();
321 let mut expired_secrets = 0;
322 let mut expiring_soon = 0; for secret in cache.values() {
325 if let Some(expires_at) = secret.expires_at {
326 if now > expires_at {
327 expired_secrets += 1;
328 } else if expires_at - now < chrono::Duration::hours(24) {
329 expiring_soon += 1;
330 }
331 }
332 }
333
334 SecretsStatistics {
335 total_secrets,
336 expired_secrets,
337 expiring_soon,
338 vault_enabled: self.config.vault_enabled,
339 env_fallback_enabled: self.config.env_fallback,
340 }
341 }
342
343 pub async fn test_connection(&self) -> Result<()> {
345 if self.config.vault_enabled {
346 if let Some(vault_address) = &self.config.vault_address {
348 debug!("Testing Vault connection to: {}", vault_address);
349 Ok(())
351 } else {
352 Err(SecurityError::SecretsError {
353 message: "Vault address not configured".to_string(),
354 })
355 }
356 } else {
357 match std::env::var("PATH") {
359 Ok(_) => {
360 debug!("Environment variable access test passed");
361 Ok(())
362 }
363 Err(_) => Err(SecurityError::SecretsError {
364 message: "Cannot access environment variables".to_string(),
365 }),
366 }
367 }
368 }
369
370 pub fn is_vault_enabled(&self) -> bool {
371 self.config.vault_enabled
372 }
373
374 pub fn is_env_fallback_enabled(&self) -> bool {
375 self.config.env_fallback
376 }
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct SecretsStatistics {
382 pub total_secrets: usize,
383 pub expired_secrets: usize,
384 pub expiring_soon: usize,
385 pub vault_enabled: bool,
386 pub env_fallback_enabled: bool,
387}
388
389impl SecretsManager {
391 pub fn generate_random_key(length: usize) -> String {
393 use rand::Rng;
394 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
395
396 let mut rng = rand::thread_rng();
397 (0..length)
398 .map(|_| {
399 let idx = rng.gen_range(0..CHARSET.len());
400 CHARSET[idx] as char
401 })
402 .collect()
403 }
404
405 pub fn generate_jwt_secret() -> String {
407 Self::generate_random_key(64)
408 }
409
410 pub fn generate_api_key() -> String {
412 format!("ak_{}", Self::generate_random_key(32))
413 }
414
415 pub fn generate_encryption_key() -> String {
417 Self::generate_random_key(32)
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use std::path::PathBuf;
425 use tempfile::tempdir;
426
427 #[tokio::test]
428 async fn test_secrets_manager_creation() {
429 let config = SecretsConfig::default();
430 let manager = SecretsManager::new(config).unwrap();
431 assert!(!manager.is_vault_enabled());
432 assert!(manager.is_env_fallback_enabled());
433 }
434
435 #[tokio::test]
436 async fn test_secret_operations() {
437 let config = SecretsConfig::default();
438 let manager = SecretsManager::new(config).unwrap();
439
440 let result = manager.set_secret("test_key", "test_value", None).await;
442 assert!(result.is_ok());
443
444 let value = manager.get_secret("test_key").await.unwrap();
446 assert_eq!(value, "test_value");
447
448 let metadata = manager.get_secret_metadata("test_key").await.unwrap();
450 assert_eq!(metadata.key, "test_key");
451 assert_eq!(metadata.version, 1);
452
453 let result = manager.delete_secret("test_key").await;
455 assert!(result.is_ok());
456
457 let result = manager.get_secret("test_key").await;
459 assert!(result.is_err());
460 }
461
462 #[tokio::test]
463 async fn test_secret_expiration() {
464 let config = SecretsConfig::default();
465 let manager = SecretsManager::new(config).unwrap();
466
467 manager
469 .set_secret("expiring_key", "expiring_value", Some(1))
470 .await
471 .unwrap();
472
473 let value = manager.get_secret("expiring_key").await.unwrap();
475 assert_eq!(value, "expiring_value");
476
477 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
479
480 let result = manager.get_secret("expiring_key").await;
482 assert!(result.is_err());
483
484 if let Err(SecurityError::SecretsError { message }) = result {
485 assert!(message.contains("expired"));
486 }
487 }
488
489 #[tokio::test]
490 async fn test_secret_rotation() {
491 let config = SecretsConfig::default();
492 let manager = SecretsManager::new(config).unwrap();
493
494 manager
496 .set_secret("rotating_key", "value_v1", None)
497 .await
498 .unwrap();
499
500 let new_version = manager
502 .rotate_secret("rotating_key", "value_v2")
503 .await
504 .unwrap();
505 assert_eq!(new_version, 2);
506
507 let value = manager.get_secret("rotating_key").await.unwrap();
509 assert_eq!(value, "value_v2");
510
511 let metadata = manager.get_secret_metadata("rotating_key").await.unwrap();
513 assert_eq!(metadata.version, 2);
514 }
515
516 #[tokio::test]
517 async fn test_cleanup_expired_secrets() {
518 let config = SecretsConfig::default();
519 let manager = SecretsManager::new(config).unwrap();
520
521 manager
523 .set_secret("permanent", "value", None)
524 .await
525 .unwrap();
526 manager
527 .set_secret("short_lived", "value", Some(1))
528 .await
529 .unwrap();
530 manager
531 .set_secret("medium_lived", "value", Some(10))
532 .await
533 .unwrap();
534
535 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
537
538 let removed_count = manager.cleanup_expired_secrets().await.unwrap();
540 assert_eq!(removed_count, 1);
541
542 let secrets = manager.list_secrets().await;
544 assert_eq!(secrets.len(), 2);
545 assert!(secrets.contains(&"permanent".to_string()));
546 assert!(secrets.contains(&"medium_lived".to_string()));
547 }
548
549 #[tokio::test]
550 async fn test_get_statistics() {
551 let config = SecretsConfig::default();
552 let manager = SecretsManager::new(config).unwrap();
553
554 manager.set_secret("secret1", "value1", None).await.unwrap();
556 manager
557 .set_secret("secret2", "value2", Some(1))
558 .await
559 .unwrap(); tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
563
564 let stats = manager.get_statistics().await;
565 assert_eq!(stats.total_secrets, 2);
566 assert_eq!(stats.expired_secrets, 1);
567 assert!(stats.env_fallback_enabled);
568 assert!(!stats.vault_enabled);
569 }
570
571 #[tokio::test]
572 async fn test_environment_fallback() {
573 std::env::set_var("TEST_SECRET_KEY", "test_secret_value");
575
576 let config = SecretsConfig {
577 vault_enabled: false,
578 vault_address: None,
579 vault_token_path: None,
580 env_fallback: true,
581 };
582
583 let manager = SecretsManager::new(config).unwrap();
584
585 let value = manager.get_secret("TEST_SECRET_KEY").await.unwrap();
587 assert_eq!(value, "test_secret_value");
588
589 std::env::remove_var("TEST_SECRET_KEY");
591 }
592
593 #[tokio::test]
594 async fn test_vault_token_path_validation() {
595 let temp_dir = tempdir().unwrap();
596 let token_path = temp_dir.path().join("vault_token");
597
598 std::fs::write(&token_path, "mock_vault_token").unwrap();
600
601 let config = SecretsConfig {
602 vault_enabled: true,
603 vault_address: Some("https://vault.example.com".to_string()),
604 vault_token_path: Some(token_path),
605 env_fallback: false,
606 };
607
608 let manager = SecretsManager::new(config).unwrap();
609
610 let result = manager.initialize().await;
612 assert!(result.is_ok());
613 }
614
615 #[tokio::test]
616 async fn test_vault_missing_token() {
617 let config = SecretsConfig {
618 vault_enabled: true,
619 vault_address: Some("https://vault.example.com".to_string()),
620 vault_token_path: Some(PathBuf::from("/nonexistent/path")),
621 env_fallback: false,
622 };
623
624 let manager = SecretsManager::new(config).unwrap();
625
626 let result = manager.initialize().await;
628 assert!(result.is_err());
629
630 if let Err(SecurityError::SecretsError { message }) = result {
631 assert!(message.contains("token file not found"));
632 }
633 }
634
635 #[test]
636 fn test_random_key_generation() {
637 let key1 = SecretsManager::generate_random_key(32);
638 let key2 = SecretsManager::generate_random_key(32);
639
640 assert_eq!(key1.len(), 32);
641 assert_eq!(key2.len(), 32);
642 assert_ne!(key1, key2); for ch in key1.chars() {
646 assert!(ch.is_ascii_alphanumeric());
647 }
648 }
649
650 #[test]
651 fn test_jwt_secret_generation() {
652 let secret = SecretsManager::generate_jwt_secret();
653 assert_eq!(secret.len(), 64);
654 }
655
656 #[test]
657 fn test_api_key_generation() {
658 let key = SecretsManager::generate_api_key();
659 assert!(key.starts_with("ak_"));
660 assert_eq!(key.len(), 35); }
662
663 #[test]
664 fn test_encryption_key_generation() {
665 let key = SecretsManager::generate_encryption_key();
666 assert_eq!(key.len(), 32);
667 }
668}