1use crate::{
4 config::StorageConfig,
5 models::{ApiKey, SecureApiKey},
6};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use thiserror::Error;
12use tokio::fs;
13use tracing::{debug, error, info, warn};
14
15#[derive(Debug, Error)]
16pub enum StorageError {
17 #[error("Storage error: {0}")]
18 General(String),
19
20 #[error("File I/O error: {0}")]
21 Io(#[from] std::io::Error),
22
23 #[error("Serialization error: {0}")]
24 Serialization(#[from] serde_json::Error),
25
26 #[error("Permission error: {0}")]
27 Permission(String),
28
29 #[error("Encryption error: {0}")]
30 Encryption(#[from] crate::crypto::encryption::EncryptionError),
31}
32
33#[async_trait]
35pub trait StorageBackend: Send + Sync {
36 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError>;
37 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError>;
38 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError>;
39 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError>;
40}
41
42pub async fn create_storage_backend(
44 config: &StorageConfig,
45) -> Result<Arc<dyn StorageBackend>, StorageError> {
46 match config {
47 StorageConfig::File {
48 path,
49 file_permissions,
50 dir_permissions,
51 require_secure_filesystem,
52 enable_filesystem_monitoring,
53 } => {
54 let storage = FileStorage::new(
55 path.clone(),
56 *file_permissions,
57 *dir_permissions,
58 *require_secure_filesystem,
59 *enable_filesystem_monitoring,
60 )
61 .await?;
62 Ok(Arc::new(storage))
63 }
64 StorageConfig::Environment { prefix } => {
65 let storage = EnvironmentStorage::new(prefix.clone());
66 Ok(Arc::new(storage))
67 }
68 StorageConfig::Memory => {
69 let storage = MemoryStorage::new();
70 Ok(Arc::new(storage))
71 }
72 }
73}
74
75pub struct FileStorage {
77 path: PathBuf,
78 encryption_key: [u8; 32],
79 file_permissions: u32,
80 #[allow(dead_code)]
81 dir_permissions: u32,
82 #[allow(dead_code)]
83 require_secure_filesystem: bool,
84 enable_filesystem_monitoring: bool,
85}
86
87impl FileStorage {
88 pub async fn new(
89 path: PathBuf,
90 file_permissions: u32,
91 dir_permissions: u32,
92 require_secure_filesystem: bool,
93 enable_filesystem_monitoring: bool,
94 ) -> Result<Self, StorageError> {
95 use crate::crypto::encryption::derive_encryption_key;
96 use crate::crypto::keys::generate_master_key;
97
98 if require_secure_filesystem {
100 Self::validate_filesystem_security(&path).await?;
101 }
102
103 if let Some(parent) = path.parent() {
105 fs::create_dir_all(parent).await?;
106
107 #[cfg(unix)]
109 {
110 use std::os::unix::fs::PermissionsExt;
111 let mut perms = fs::metadata(parent).await?.permissions();
112 perms.set_mode(dir_permissions); fs::set_permissions(parent, perms).await?;
114
115 Self::verify_directory_security(parent, dir_permissions).await?;
117 }
118 }
119
120 let master_key = generate_master_key().map_err(|e| StorageError::General(e.to_string()))?;
122 let encryption_key = derive_encryption_key(&master_key, "api-key-storage");
123
124 let storage = Self {
125 path,
126 encryption_key,
127 file_permissions,
128 dir_permissions,
129 require_secure_filesystem,
130 enable_filesystem_monitoring,
131 };
132
133 if !storage.path.exists() {
135 storage.save_all_keys(&HashMap::new()).await?;
136 } else {
137 storage.ensure_secure_permissions().await?;
139 }
140
141 Ok(storage)
142 }
143
144 async fn ensure_secure_permissions(&self) -> Result<(), StorageError> {
145 #[cfg(unix)]
146 {
147 use std::os::unix::fs::PermissionsExt;
148
149 if self.path.exists() {
150 let metadata = fs::metadata(&self.path).await?;
151 let mode = metadata.permissions().mode() & 0o777;
152
153 if mode != self.file_permissions {
155 warn!(
156 "Incorrect permissions on key file: {:o}, fixing to {:o}",
157 mode, self.file_permissions
158 );
159 let mut perms = metadata.permissions();
160 perms.set_mode(self.file_permissions);
161 fs::set_permissions(&self.path, perms).await?;
162 }
163
164 Self::verify_file_ownership(&self.path).await?;
166 }
167 }
168 Ok(())
169 }
170
171 async fn validate_filesystem_security(path: &PathBuf) -> Result<(), StorageError> {
173 #[cfg(unix)]
174 {
175 use std::os::unix::fs::MetadataExt;
176
177 if let Some(parent) = path.parent() {
178 if parent.exists() {
179 let metadata = fs::metadata(parent).await?;
180
181 let _dev = metadata.dev();
183
184 if let Ok(mount_info) = fs::read_to_string("/proc/mounts").await {
187 let path_str = parent.to_string_lossy();
188 for line in mount_info.lines() {
189 let parts: Vec<&str> = line.split_whitespace().collect();
190 if parts.len() >= 3 {
191 let mount_point = parts[1];
192 let fs_type = parts[2];
193
194 if path_str.starts_with(mount_point) {
195 match fs_type {
197 "nfs" | "nfs4" | "cifs" | "smb" | "smbfs"
198 | "fuse.sshfs" => {
199 return Err(StorageError::Permission(format!(
200 "Storage path {} is on insecure network filesystem: {}",
201 path_str, fs_type
202 )));
203 }
204 _ => {}
205 }
206 }
207 }
208 }
209 }
210 }
211 }
212 }
213
214 Ok(())
215 }
216
217 async fn verify_directory_security(
219 dir: &std::path::Path,
220 expected_perms: u32,
221 ) -> Result<(), StorageError> {
222 #[cfg(unix)]
223 {
224 use std::os::unix::fs::{MetadataExt, PermissionsExt};
225
226 let metadata = fs::metadata(dir).await?;
227 let mode = metadata.permissions().mode() & 0o777;
228
229 if (mode & !expected_perms) != 0 {
231 return Err(StorageError::Permission(format!(
232 "Directory {} has insecure permissions: {:o} (expected: {:o})",
233 dir.display(),
234 mode,
235 expected_perms
236 )));
237 }
238
239 let current_uid = unsafe { libc::getuid() };
241 if metadata.uid() != current_uid {
242 return Err(StorageError::Permission(format!(
243 "Directory {} is not owned by current user (uid: {} vs {})",
244 dir.display(),
245 metadata.uid(),
246 current_uid
247 )));
248 }
249 }
250
251 Ok(())
252 }
253
254 async fn verify_file_ownership(file: &std::path::Path) -> Result<(), StorageError> {
256 #[cfg(unix)]
257 {
258 use std::os::unix::fs::MetadataExt;
259
260 let metadata = fs::metadata(file).await?;
261 let current_uid = unsafe { libc::getuid() };
262
263 if metadata.uid() != current_uid {
264 return Err(StorageError::Permission(format!(
265 "File {} is not owned by current user (uid: {} vs {})",
266 file.display(),
267 metadata.uid(),
268 current_uid
269 )));
270 }
271 }
272
273 Ok(())
274 }
275
276 async fn save_secure_keys(
278 &self,
279 keys: &HashMap<String, SecureApiKey>,
280 ) -> Result<(), StorageError> {
281 use crate::crypto::encryption::encrypt_data;
282
283 let content = serde_json::to_string_pretty(keys)?;
284 let encrypted_data = encrypt_data(content.as_bytes(), &self.encryption_key)?;
285 let encrypted_content = serde_json::to_string_pretty(&encrypted_data)?;
286
287 let temp_path = self.path.with_extension("tmp");
289 fs::write(&temp_path, encrypted_content).await?;
290
291 #[cfg(unix)]
293 {
294 use std::os::unix::fs::PermissionsExt;
295 let mut perms = fs::metadata(&temp_path).await?.permissions();
296 perms.set_mode(self.file_permissions); fs::set_permissions(&temp_path, perms).await?;
298 }
299
300 fs::rename(&temp_path, &self.path).await?;
302
303 debug!("Saved {} keys to encrypted file storage", keys.len());
304 Ok(())
305 }
306
307 pub async fn create_backup(&self) -> Result<PathBuf, StorageError> {
309 if !self.path.exists() {
310 return Err(StorageError::General(
311 "Storage file does not exist".to_string(),
312 ));
313 }
314
315 let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
316 let backup_path = self
317 .path
318 .with_extension(format!("backup_{}.enc", timestamp));
319
320 fs::copy(&self.path, &backup_path).await?;
322
323 #[cfg(unix)]
324 {
325 use std::os::unix::fs::PermissionsExt;
326 let mut perms = fs::metadata(&backup_path).await?.permissions();
327 perms.set_mode(self.file_permissions);
328 fs::set_permissions(&backup_path, perms).await?;
329 }
330
331 debug!("Created secure backup: {}", backup_path.display());
332 Ok(backup_path)
333 }
334
335 pub async fn restore_from_backup(&self, backup_path: &PathBuf) -> Result<(), StorageError> {
337 if !backup_path.exists() {
338 return Err(StorageError::General(
339 "Backup file does not exist".to_string(),
340 ));
341 }
342
343 Self::verify_file_ownership(backup_path).await?;
345
346 let temp_path = self.path.with_extension("restore_tmp");
348 fs::copy(backup_path, &temp_path).await?;
349
350 #[cfg(unix)]
351 {
352 use std::os::unix::fs::PermissionsExt;
353 let mut perms = fs::metadata(&temp_path).await?.permissions();
354 perms.set_mode(self.file_permissions);
355 fs::set_permissions(&temp_path, perms).await?;
356 }
357
358 fs::rename(&temp_path, &self.path).await?;
360
361 info!("Restored from backup: {}", backup_path.display());
362 Ok(())
363 }
364
365 pub async fn cleanup_backups(&self, keep_count: usize) -> Result<(), StorageError> {
367 if let Some(parent) = self.path.parent() {
368 let filename_stem = self
369 .path
370 .file_stem()
371 .and_then(|s| s.to_str())
372 .unwrap_or("keys");
373
374 let mut backups = Vec::new();
375 let mut entries = fs::read_dir(parent).await?;
376
377 while let Some(entry) = entries.next_entry().await? {
378 let path = entry.path();
379 if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
380 if filename.starts_with(&format!("{}.backup_", filename_stem)) {
381 if let Ok(metadata) = entry.metadata().await {
382 backups.push((
383 path,
384 metadata
385 .modified()
386 .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
387 ));
388 }
389 }
390 }
391 }
392
393 backups.sort_by(|a, b| b.1.cmp(&a.1));
395
396 for (backup_path, _) in backups.iter().skip(keep_count) {
398 if let Err(e) = fs::remove_file(backup_path).await {
399 warn!(
400 "Failed to remove old backup {}: {}",
401 backup_path.display(),
402 e
403 );
404 } else {
405 debug!("Removed old backup: {}", backup_path.display());
406 }
407 }
408 }
409
410 Ok(())
411 }
412
413 #[cfg(target_os = "linux")]
415 pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
416 if !self.enable_filesystem_monitoring {
417 return Ok(());
418 }
419
420 use inotify::{Inotify, WatchMask};
421
422 let mut inotify = Inotify::init()
423 .map_err(|e| StorageError::General(format!("Failed to initialize inotify: {}", e)))?;
424
425 if let Some(parent) = self.path.parent() {
427 inotify
428 .watches()
429 .add(
430 parent,
431 WatchMask::MODIFY | WatchMask::ATTRIB | WatchMask::MOVED_TO | WatchMask::DELETE,
432 )
433 .map_err(|e| {
434 StorageError::General(format!("Failed to add inotify watch: {}", e))
435 })?;
436
437 info!("Started filesystem monitoring for: {}", parent.display());
438
439 let path = self.path.clone();
441 let file_permissions = self.file_permissions;
442
443 tokio::spawn(async move {
444 use tracing::{error, warn};
445 let mut buffer = [0; 1024];
446 loop {
447 match inotify.read_events_blocking(&mut buffer) {
448 Ok(events) => {
449 for event in events {
450 if let Some(name) = event.name {
451 if name.to_string_lossy().contains("keys") {
452 warn!(
453 "Detected unauthorized change to auth storage: {:?} (mask: {:?})",
454 name, event.mask
455 );
456
457 if path.exists() {
459 #[cfg(unix)]
460 {
461 use std::os::unix::fs::PermissionsExt;
462 if let Ok(metadata) = std::fs::metadata(&path) {
463 let mode =
464 metadata.permissions().mode() & 0o777;
465 if mode != file_permissions {
466 error!(
467 "Security violation: File permissions changed from {:o} to {:o}",
468 file_permissions, mode
469 );
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477 }
478 Err(e) => {
479 error!("Error reading inotify events: {}", e);
480 break;
481 }
482 }
483 }
484 });
485 }
486
487 Ok(())
488 }
489
490 #[cfg(not(target_os = "linux"))]
492 pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
493 if self.enable_filesystem_monitoring {
494 warn!("Filesystem monitoring is only supported on Linux systems");
495 }
496 Ok(())
497 }
498}
499
500#[async_trait]
501impl StorageBackend for FileStorage {
502 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
503 use crate::crypto::encryption::decrypt_data;
504
505 self.ensure_secure_permissions().await?;
506
507 if !self.path.exists() {
508 return Ok(HashMap::new());
509 }
510
511 let content = fs::read(&self.path).await?;
512 if content.is_empty() {
513 return Ok(HashMap::new());
514 }
515
516 let decrypted_content = if let Ok(encrypted_data) = serde_json::from_slice(&content) {
518 let decrypted_bytes = decrypt_data(&encrypted_data, &self.encryption_key)?;
520 String::from_utf8(decrypted_bytes)
521 .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?
522 } else {
523 let plain_text = String::from_utf8(content)
525 .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?;
526 warn!("Found legacy plain text keys, converting to secure format");
527
528 let legacy_keys: HashMap<String, ApiKey> = serde_json::from_str(&plain_text)?;
530 let secure_keys: HashMap<String, SecureApiKey> = legacy_keys
531 .into_iter()
532 .map(|(id, key)| (id, key.to_secure_storage()))
533 .collect();
534
535 self.save_secure_keys(&secure_keys).await?;
537
538 plain_text
540 };
541
542 let secure_keys: HashMap<String, SecureApiKey> = serde_json::from_str(&decrypted_content)?;
544
545 let keys: HashMap<String, ApiKey> = secure_keys
547 .into_iter()
548 .map(|(id, secure_key)| (id, secure_key.to_api_key()))
549 .collect();
550
551 debug!("Loaded {} keys from encrypted file storage", keys.len());
552 Ok(keys)
553 }
554
555 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
556 let mut keys = self.load_keys().await?;
557 keys.insert(key.id.clone(), key.clone());
558 self.save_all_keys(&keys).await
559 }
560
561 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
562 let mut keys = self.load_keys().await?;
563 keys.remove(key_id);
564 self.save_all_keys(&keys).await
565 }
566
567 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
568 let secure_keys: HashMap<String, SecureApiKey> = keys
570 .iter()
571 .map(|(id, key)| (id.clone(), key.to_secure_storage()))
572 .collect();
573
574 self.save_secure_keys(&secure_keys).await
575 }
576}
577
578pub struct EnvironmentStorage {
580 var_name: String,
581}
582
583impl EnvironmentStorage {
584 pub fn new(var_name: String) -> Self {
585 Self { var_name }
586 }
587}
588
589#[async_trait]
590impl StorageBackend for EnvironmentStorage {
591 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
592 match std::env::var(&self.var_name) {
593 Ok(content) => {
594 if content.trim().is_empty() {
595 return Ok(HashMap::new());
596 }
597 let keys: HashMap<String, ApiKey> = serde_json::from_str(&content)?;
598 debug!("Loaded {} keys from environment storage", keys.len());
599 Ok(keys)
600 }
601 Err(_) => {
602 debug!(
603 "Environment variable {} not found, returning empty keys",
604 self.var_name
605 );
606 Ok(HashMap::new())
607 }
608 }
609 }
610
611 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
612 let mut keys = self.load_keys().await?;
613 keys.insert(key.id.clone(), key.clone());
614 self.save_all_keys(&keys).await
615 }
616
617 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
618 let mut keys = self.load_keys().await?;
619 keys.remove(key_id);
620 self.save_all_keys(&keys).await
621 }
622
623 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
624 let content = serde_json::to_string(keys)?;
625 std::env::set_var(&self.var_name, content);
626
627 debug!("Saved {} keys to environment storage", keys.len());
628 Ok(())
629 }
630}
631
632pub struct MemoryStorage {
634 keys: tokio::sync::RwLock<HashMap<String, ApiKey>>,
635}
636
637impl MemoryStorage {
638 pub fn new() -> Self {
639 Self {
640 keys: tokio::sync::RwLock::new(HashMap::new()),
641 }
642 }
643}
644
645#[async_trait]
646impl StorageBackend for MemoryStorage {
647 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
648 let keys = self.keys.read().await;
649 debug!("Loaded {} keys from memory storage", keys.len());
650 Ok(keys.clone())
651 }
652
653 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
654 let mut keys = self.keys.write().await;
655 keys.insert(key.id.clone(), key.clone());
656 debug!("Saved key {} to memory storage", key.id);
657 Ok(())
658 }
659
660 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
661 let mut keys = self.keys.write().await;
662 keys.remove(key_id);
663 debug!("Deleted key {} from memory storage", key_id);
664 Ok(())
665 }
666
667 async fn save_all_keys(&self, new_keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
668 let mut keys = self.keys.write().await;
669 *keys = new_keys.clone();
670 debug!(
671 "Replaced all keys in memory storage with {} keys",
672 new_keys.len()
673 );
674 Ok(())
675 }
676}