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_%3f");
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}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use crate::models::{ApiKey, Role};
682 use chrono::{Duration, Utc};
683 use std::collections::HashMap;
684 use tempfile::TempDir;
685 use tokio::fs;
686
687 fn create_test_key(name: &str, role: Role) -> ApiKey {
689 ApiKey::new(
690 name.to_string(),
691 role,
692 Some(Utc::now() + Duration::days(30)),
693 vec!["127.0.0.1".to_string()],
694 )
695 }
696
697 fn create_test_keys() -> HashMap<String, ApiKey> {
699 let mut keys = HashMap::new();
700
701 let admin_key = create_test_key("admin-key", Role::Admin);
702 let operator_key = create_test_key("operator-key", Role::Operator);
703 let monitor_key = create_test_key("monitor-key", Role::Monitor);
704
705 keys.insert(admin_key.id.clone(), admin_key);
706 keys.insert(operator_key.id.clone(), operator_key);
707 keys.insert(monitor_key.id.clone(), monitor_key);
708
709 keys
710 }
711
712 #[test]
713 fn test_storage_error_display() {
714 let error = StorageError::General("test error".to_string());
715 assert_eq!(error.to_string(), "Storage error: test error");
716
717 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
718 let storage_error = StorageError::Io(io_error);
719 assert!(storage_error.to_string().contains("File I/O error"));
720
721 let perm_error = StorageError::Permission("access denied".to_string());
722 assert_eq!(perm_error.to_string(), "Permission error: access denied");
723 }
724
725 #[test]
726 fn test_storage_error_from_io_error() {
727 let io_error =
728 std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
729 let storage_error: StorageError = io_error.into();
730
731 match storage_error {
732 StorageError::Io(_) => (),
733 _ => panic!("Expected Io variant"),
734 }
735 }
736
737 #[test]
738 fn test_storage_error_from_serde_error() {
739 let serde_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
740 let storage_error: StorageError = serde_error.into();
741
742 match storage_error {
743 StorageError::Serialization(_) => (),
744 _ => panic!("Expected Serialization variant"),
745 }
746 }
747
748 mod memory_storage_tests {
749 use super::*;
750
751 #[tokio::test]
752 async fn test_memory_storage_new() {
753 let storage = MemoryStorage::new();
754 let keys = storage.load_keys().await.unwrap();
755 assert!(keys.is_empty());
756 }
757
758 #[tokio::test]
759 async fn test_memory_storage_save_and_load_key() {
760 let storage = MemoryStorage::new();
761 let test_key = create_test_key("test-key", Role::Operator);
762
763 storage.save_key(&test_key).await.unwrap();
764
765 let keys = storage.load_keys().await.unwrap();
766 assert_eq!(keys.len(), 1);
767 assert!(keys.contains_key(&test_key.id));
768
769 let loaded_key = &keys[&test_key.id];
770 assert_eq!(loaded_key.name, test_key.name);
771 assert_eq!(loaded_key.role, test_key.role);
772 }
773
774 #[tokio::test]
775 async fn test_memory_storage_save_multiple_keys() {
776 let storage = MemoryStorage::new();
777 let test_keys = create_test_keys();
778
779 for key in test_keys.values() {
780 storage.save_key(key).await.unwrap();
781 }
782
783 let loaded_keys = storage.load_keys().await.unwrap();
784 assert_eq!(loaded_keys.len(), test_keys.len());
785
786 for (id, key) in test_keys.iter() {
787 assert!(loaded_keys.contains_key(id));
788 assert_eq!(loaded_keys[id].name, key.name);
789 }
790 }
791
792 #[tokio::test]
793 async fn test_memory_storage_delete_key() {
794 let storage = MemoryStorage::new();
795 let test_key = create_test_key("test-key", Role::Monitor);
796
797 storage.save_key(&test_key).await.unwrap();
798 assert_eq!(storage.load_keys().await.unwrap().len(), 1);
799
800 storage.delete_key(&test_key.id).await.unwrap();
801 let keys = storage.load_keys().await.unwrap();
802 assert!(keys.is_empty());
803 }
804
805 #[tokio::test]
806 async fn test_memory_storage_delete_nonexistent_key() {
807 let storage = MemoryStorage::new();
808
809 storage.delete_key("nonexistent").await.unwrap();
811 assert!(storage.load_keys().await.unwrap().is_empty());
812 }
813
814 #[tokio::test]
815 async fn test_memory_storage_save_all_keys() {
816 let storage = MemoryStorage::new();
817 let test_keys = create_test_keys();
818
819 storage.save_all_keys(&test_keys).await.unwrap();
820
821 let loaded_keys = storage.load_keys().await.unwrap();
822 assert_eq!(loaded_keys.len(), test_keys.len());
823
824 for (id, key) in test_keys.iter() {
825 assert!(loaded_keys.contains_key(id));
826 assert_eq!(loaded_keys[id].name, key.name);
827 }
828 }
829
830 #[tokio::test]
831 async fn test_memory_storage_save_all_keys_replaces_existing() {
832 let storage = MemoryStorage::new();
833
834 let initial_keys = create_test_keys();
836 storage.save_all_keys(&initial_keys).await.unwrap();
837 assert_eq!(storage.load_keys().await.unwrap().len(), initial_keys.len());
838
839 let mut new_keys = HashMap::new();
841 let new_key = create_test_key("new-key", Role::Admin);
842 new_keys.insert(new_key.id.clone(), new_key);
843
844 storage.save_all_keys(&new_keys).await.unwrap();
845
846 let loaded_keys = storage.load_keys().await.unwrap();
847 assert_eq!(loaded_keys.len(), 1);
848 assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
849 }
850
851 #[tokio::test]
852 async fn test_memory_storage_concurrent_access() {
853 let storage = std::sync::Arc::new(MemoryStorage::new());
854 let mut handles = vec![];
855
856 for i in 0..10 {
858 let storage_clone = storage.clone();
859 let handle = tokio::spawn(async move {
860 let key = create_test_key(&format!("key-{}", i), Role::Operator);
861 storage_clone.save_key(&key).await.unwrap();
862 key.id
863 });
864 handles.push(handle);
865 }
866
867 let mut saved_ids = vec![];
868 for handle in handles {
869 saved_ids.push(handle.await.unwrap());
870 }
871
872 let keys = storage.load_keys().await.unwrap();
873 assert_eq!(keys.len(), 10);
874
875 for id in saved_ids {
876 assert!(keys.contains_key(&id));
877 }
878 }
879 }
880
881 mod environment_storage_tests {
882 use super::*;
883
884 #[tokio::test]
885 async fn test_environment_storage_new() {
886 let storage = EnvironmentStorage::new("TEST_MCP_KEYS".to_string());
887
888 std::env::remove_var("TEST_MCP_KEYS");
890
891 let keys = storage.load_keys().await.unwrap();
892 assert!(keys.is_empty());
893 }
894
895 #[tokio::test]
896 async fn test_environment_storage_save_and_load_key() {
897 let var_name = "TEST_MCP_KEYS_SAVE_LOAD";
898 std::env::remove_var(var_name);
899
900 let storage = EnvironmentStorage::new(var_name.to_string());
901 let test_key = create_test_key("env-test-key", Role::Monitor);
902
903 storage.save_key(&test_key).await.unwrap();
904
905 let keys = storage.load_keys().await.unwrap();
906 assert_eq!(keys.len(), 1);
907 assert!(keys.contains_key(&test_key.id));
908
909 assert!(std::env::var(var_name).is_ok());
911
912 std::env::remove_var(var_name);
914 }
915
916 #[tokio::test]
917 async fn test_environment_storage_multiple_keys() {
918 let var_name = "TEST_MCP_KEYS_MULTIPLE";
919 std::env::remove_var(var_name);
920
921 let storage = EnvironmentStorage::new(var_name.to_string());
922 let test_keys = create_test_keys();
923
924 storage.save_all_keys(&test_keys).await.unwrap();
925
926 let loaded_keys = storage.load_keys().await.unwrap();
927 assert_eq!(loaded_keys.len(), test_keys.len());
928
929 for (id, key) in test_keys.iter() {
930 assert!(loaded_keys.contains_key(id));
931 assert_eq!(loaded_keys[id].name, key.name);
932 }
933
934 std::env::remove_var(var_name);
936 }
937
938 #[tokio::test]
939 async fn test_environment_storage_delete_key() {
940 let var_name = "TEST_MCP_KEYS_DELETE";
941 std::env::remove_var(var_name);
942
943 let storage = EnvironmentStorage::new(var_name.to_string());
944 let test_keys = create_test_keys();
945 let key_to_delete = test_keys.values().next().unwrap().id.clone();
946
947 storage.save_all_keys(&test_keys).await.unwrap();
948 assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
949
950 storage.delete_key(&key_to_delete).await.unwrap();
951
952 let remaining_keys = storage.load_keys().await.unwrap();
953 assert_eq!(remaining_keys.len(), test_keys.len() - 1);
954 assert!(!remaining_keys.contains_key(&key_to_delete));
955
956 std::env::remove_var(var_name);
958 }
959
960 #[tokio::test]
961 async fn test_environment_storage_empty_content() {
962 let var_name = "TEST_MCP_KEYS_EMPTY";
963 std::env::set_var(var_name, "");
964
965 let storage = EnvironmentStorage::new(var_name.to_string());
966 let keys = storage.load_keys().await.unwrap();
967 assert!(keys.is_empty());
968
969 std::env::remove_var(var_name);
971 }
972
973 #[tokio::test]
974 async fn test_environment_storage_invalid_json() {
975 let var_name = "TEST_MCP_KEYS_INVALID";
976 std::env::set_var(var_name, "invalid json content");
977
978 let storage = EnvironmentStorage::new(var_name.to_string());
979 let result = storage.load_keys().await;
980
981 assert!(result.is_err());
982 match result.unwrap_err() {
983 StorageError::Serialization(_) => (),
984 _ => panic!("Expected serialization error"),
985 }
986
987 std::env::remove_var(var_name);
989 }
990
991 #[tokio::test]
992 async fn test_environment_storage_overwrite_existing() {
993 let var_name = "TEST_MCP_KEYS_OVERWRITE";
994 std::env::remove_var(var_name);
995
996 let storage = EnvironmentStorage::new(var_name.to_string());
997
998 let initial_keys = create_test_keys();
1000 storage.save_all_keys(&initial_keys).await.unwrap();
1001
1002 let mut new_keys = HashMap::new();
1004 let new_key = create_test_key("overwrite-key", Role::Admin);
1005 new_keys.insert(new_key.id.clone(), new_key);
1006
1007 storage.save_all_keys(&new_keys).await.unwrap();
1008
1009 let loaded_keys = storage.load_keys().await.unwrap();
1010 assert_eq!(loaded_keys.len(), 1);
1011 assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
1012
1013 std::env::remove_var(var_name);
1015 }
1016 }
1017
1018 mod file_storage_tests {
1019 use super::*;
1020
1021 async fn create_test_file_storage() -> (FileStorage, TempDir) {
1022 std::env::set_var(
1024 "PULSEENGINE_MCP_MASTER_KEY",
1025 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1026 );
1027 let temp_dir = TempDir::new().unwrap();
1028 let storage_path = temp_dir.path().join("test_keys.enc");
1029
1030 let storage = FileStorage::new(
1031 storage_path,
1032 0o600,
1033 0o700,
1034 false, false, )
1037 .await
1038 .unwrap();
1039
1040 (storage, temp_dir)
1041 }
1042
1043 #[tokio::test]
1044 async fn test_file_storage_new() {
1045 let (storage, _temp_dir) = create_test_file_storage().await;
1046
1047 let keys = storage.load_keys().await.unwrap();
1049 assert!(keys.is_empty());
1050
1051 assert!(storage.path.exists());
1053 }
1054
1055 #[tokio::test]
1056 async fn test_file_storage_save_and_load_key() {
1057 let (storage, _temp_dir) = create_test_file_storage().await;
1058 let test_key = create_test_key("file-test-key", Role::Operator);
1059
1060 storage.save_key(&test_key).await.unwrap();
1061
1062 let keys = storage.load_keys().await.unwrap();
1063 assert_eq!(keys.len(), 1);
1064 assert!(keys.contains_key(&test_key.id));
1065
1066 let loaded_key = &keys[&test_key.id];
1067 assert_eq!(loaded_key.name, test_key.name);
1068 assert_eq!(loaded_key.role, test_key.role);
1069 assert_eq!(loaded_key.key, "***redacted***");
1071 }
1072
1073 #[tokio::test]
1074 async fn test_file_storage_multiple_keys() {
1075 let (storage, _temp_dir) = create_test_file_storage().await;
1076 let test_keys = create_test_keys();
1077
1078 storage.save_all_keys(&test_keys).await.unwrap();
1079
1080 let loaded_keys = storage.load_keys().await.unwrap();
1081 assert_eq!(loaded_keys.len(), test_keys.len());
1082
1083 for (id, key) in test_keys.iter() {
1084 assert!(loaded_keys.contains_key(id));
1085 assert_eq!(loaded_keys[id].name, key.name);
1086 assert_eq!(loaded_keys[id].role, key.role);
1087 }
1088 }
1089
1090 #[tokio::test]
1091 async fn test_file_storage_delete_key() {
1092 let (storage, _temp_dir) = create_test_file_storage().await;
1093 let test_keys = create_test_keys();
1094 let key_to_delete = test_keys.values().next().unwrap().id.clone();
1095
1096 storage.save_all_keys(&test_keys).await.unwrap();
1097 assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
1098
1099 storage.delete_key(&key_to_delete).await.unwrap();
1100
1101 let remaining_keys = storage.load_keys().await.unwrap();
1102 assert_eq!(remaining_keys.len(), test_keys.len() - 1);
1103 assert!(!remaining_keys.contains_key(&key_to_delete));
1104 }
1105
1106 #[tokio::test]
1107 async fn test_file_storage_encryption() {
1108 let (storage, _temp_dir) = create_test_file_storage().await;
1109 let test_key = create_test_key("encryption-test", Role::Admin);
1110
1111 storage.save_key(&test_key).await.unwrap();
1112
1113 let raw_content = fs::read(&storage.path).await.unwrap();
1115 let raw_text = String::from_utf8_lossy(&raw_content);
1116
1117 assert!(!raw_text.contains(&test_key.name));
1119 assert!(!raw_text.contains(&test_key.key));
1120
1121 let loaded_keys = storage.load_keys().await.unwrap();
1123 assert_eq!(loaded_keys.len(), 1);
1124 assert!(loaded_keys.contains_key(&test_key.id));
1125 }
1126
1127 #[tokio::test]
1128 async fn test_file_storage_empty_file() {
1129 let temp_dir = TempDir::new().unwrap();
1130 let storage_path = temp_dir.path().join("empty_keys.enc");
1131
1132 fs::write(&storage_path, "").await.unwrap();
1134
1135 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1136 .await
1137 .unwrap();
1138
1139 let keys = storage.load_keys().await.unwrap();
1140 assert!(keys.is_empty());
1141 }
1142
1143 #[tokio::test]
1144 async fn test_file_storage_nonexistent_file() {
1145 let temp_dir = TempDir::new().unwrap();
1146 let storage_path = temp_dir.path().join("nonexistent").join("keys.enc");
1147
1148 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1150 .await
1151 .unwrap();
1152
1153 let keys = storage.load_keys().await.unwrap();
1155 assert!(keys.is_empty());
1156 assert!(storage_path.exists());
1157 }
1158
1159 #[tokio::test]
1160 async fn test_file_storage_persistence() {
1161 static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1164 let _lock = TEST_LOCK.lock().unwrap();
1165
1166 let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1168
1169 std::env::set_var(
1171 "PULSEENGINE_MCP_MASTER_KEY",
1172 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1173 );
1174
1175 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1177
1178 let temp_dir = TempDir::new().unwrap();
1179 let storage_path = temp_dir.path().join("persistent_keys.enc");
1180 let test_keys = create_test_keys();
1181
1182 {
1184 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1185 .await
1186 .unwrap();
1187
1188 storage.save_all_keys(&test_keys).await.unwrap();
1189 }
1190
1191 assert!(storage_path.exists());
1193 let file_metadata = std::fs::metadata(&storage_path).unwrap();
1194 assert!(file_metadata.len() > 0);
1195
1196 {
1198 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1199 .await
1200 .unwrap();
1201
1202 let loaded_keys = storage.load_keys().await.unwrap();
1203 assert_eq!(loaded_keys.len(), test_keys.len());
1204
1205 for (id, key) in test_keys.iter() {
1206 assert!(loaded_keys.contains_key(id));
1207 assert_eq!(loaded_keys[id].name, key.name);
1208 }
1209 }
1210
1211 match original_master_key {
1213 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1214 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1215 }
1216 }
1217
1218 #[tokio::test]
1219 async fn test_file_storage_backup_and_restore() {
1220 let (storage, _temp_dir) = create_test_file_storage().await;
1221 let test_keys = create_test_keys();
1222
1223 storage.save_all_keys(&test_keys).await.unwrap();
1225
1226 let backup_path = storage.create_backup().await.unwrap();
1228 assert!(backup_path.exists());
1229 assert!(backup_path.to_string_lossy().contains("backup_"));
1230
1231 let mut modified_keys = HashMap::new();
1233 let new_key = create_test_key("backup-test", Role::Monitor);
1234 modified_keys.insert(new_key.id.clone(), new_key);
1235 storage.save_all_keys(&modified_keys).await.unwrap();
1236
1237 assert_eq!(storage.load_keys().await.unwrap().len(), 1);
1239
1240 storage.restore_from_backup(&backup_path).await.unwrap();
1242
1243 let restored_keys = storage.load_keys().await.unwrap();
1245 assert_eq!(restored_keys.len(), test_keys.len());
1246
1247 for id in test_keys.keys() {
1248 assert!(restored_keys.contains_key(id));
1249 }
1250 }
1251
1252 #[tokio::test]
1253 async fn test_file_storage_backup_nonexistent_storage() {
1254 let temp_dir = TempDir::new().unwrap();
1255 let storage_path = temp_dir.path().join("missing_keys.enc");
1256
1257 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1258 .await
1259 .unwrap();
1260
1261 fs::remove_file(&storage.path).await.unwrap();
1263
1264 let result = storage.create_backup().await;
1265 assert!(result.is_err());
1266 match result.unwrap_err() {
1267 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1268 _ => panic!("Expected general error"),
1269 }
1270 }
1271
1272 #[tokio::test]
1273 async fn test_file_storage_restore_nonexistent_backup() {
1274 let (storage, temp_dir) = create_test_file_storage().await;
1275 let nonexistent_backup = temp_dir.path().join("nonexistent_backup.enc");
1276
1277 let result = storage.restore_from_backup(&nonexistent_backup).await;
1278 assert!(result.is_err());
1279 match result.unwrap_err() {
1280 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1281 _ => panic!("Expected general error"),
1282 }
1283 }
1284
1285 #[tokio::test]
1286 async fn test_file_storage_cleanup_backups() {
1287 static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1289 let _lock = TEST_LOCK.lock().unwrap();
1290
1291 let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1293
1294 std::env::set_var(
1296 "PULSEENGINE_MCP_MASTER_KEY",
1297 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1298 );
1299
1300 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1302
1303 let (storage, _temp_dir) = create_test_file_storage().await;
1304 let test_key = create_test_key("cleanup-test", Role::Admin);
1305
1306 storage.save_key(&test_key).await.unwrap();
1307
1308 let mut backup_paths = vec![];
1310 for _i in 0..5 {
1311 let backup_path = storage.create_backup().await.unwrap();
1312 backup_paths.push(backup_path);
1313 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1315 }
1316
1317 for (i, path) in backup_paths.iter().enumerate() {
1319 assert!(path.exists(), "Backup {} does not exist: {:?}", i, path);
1320 }
1321
1322 storage.cleanup_backups(2).await.unwrap();
1324
1325 let parent = storage.path.parent().unwrap();
1327 let mut remaining_backups = 0;
1328 let mut entries = fs::read_dir(parent).await.unwrap();
1329
1330 while let Some(entry) = entries.next_entry().await.unwrap() {
1331 if entry.file_name().to_string_lossy().contains("backup_") {
1332 remaining_backups += 1;
1333 }
1334 }
1335
1336 assert_eq!(remaining_backups, 2);
1337
1338 match original_master_key {
1340 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1341 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1342 }
1343 }
1344
1345 #[cfg(unix)]
1346 #[tokio::test]
1347 async fn test_file_storage_permissions() {
1348 use std::os::unix::fs::PermissionsExt;
1349
1350 let (storage, _temp_dir) = create_test_file_storage().await;
1351 let test_key = create_test_key("perm-test", Role::Operator);
1352
1353 storage.save_key(&test_key).await.unwrap();
1354
1355 let metadata = fs::metadata(&storage.path).await.unwrap();
1357 let mode = metadata.permissions().mode() & 0o777;
1358 assert_eq!(mode, 0o600);
1359
1360 if let Some(parent) = storage.path.parent() {
1362 let parent_metadata = fs::metadata(parent).await.unwrap();
1363 let parent_mode = parent_metadata.permissions().mode() & 0o777;
1364 assert_eq!(parent_mode, 0o700);
1365 }
1366 }
1367
1368 #[tokio::test]
1369 async fn test_file_storage_atomic_operations() {
1370 static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1372 let _lock = TEST_LOCK.lock().unwrap();
1373
1374 let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1376
1377 std::env::set_var(
1379 "PULSEENGINE_MCP_MASTER_KEY",
1380 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1381 );
1382
1383 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1385
1386 let (storage, _temp_dir) = create_test_file_storage().await;
1387 let initial_keys = create_test_keys();
1388
1389 storage.save_all_keys(&initial_keys).await.unwrap();
1390
1391 let storage_clone = std::sync::Arc::new(storage);
1393 let mut handles = vec![];
1394
1395 for i in 0..10 {
1396 let storage_ref = storage_clone.clone();
1397 let handle = tokio::spawn(async move {
1398 let key = create_test_key(&format!("concurrent-{}", i), Role::Monitor);
1399 storage_ref.save_key(&key).await
1400 });
1401 handles.push(handle);
1402 }
1403
1404 for handle in handles {
1406 let _ = handle.await;
1408 }
1409
1410 let final_keys = storage_clone.load_keys().await.unwrap();
1412 assert!(final_keys.len() >= initial_keys.len());
1413
1414 for id in initial_keys.keys() {
1416 assert!(final_keys.contains_key(id));
1417 }
1418
1419 match original_master_key {
1421 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1422 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1423 }
1424 }
1425 }
1426
1427 mod storage_factory_tests {
1428 use super::*;
1429 use crate::config::StorageConfig;
1430
1431 #[tokio::test]
1432 async fn test_create_memory_storage_backend() {
1433 let config = StorageConfig::Memory;
1434 let backend = create_storage_backend(&config).await.unwrap();
1435
1436 let test_key = create_test_key("memory-factory-test", Role::Admin);
1438 backend.save_key(&test_key).await.unwrap();
1439
1440 let keys = backend.load_keys().await.unwrap();
1441 assert_eq!(keys.len(), 1);
1442 assert!(keys.contains_key(&test_key.id));
1443 }
1444
1445 #[tokio::test]
1446 async fn test_create_environment_storage_backend() {
1447 let var_name = "TEST_FACTORY_ENV_STORAGE";
1448 std::env::remove_var(var_name);
1449
1450 let config = StorageConfig::Environment {
1451 prefix: var_name.to_string(),
1452 };
1453 let backend = create_storage_backend(&config).await.unwrap();
1454
1455 let test_key = create_test_key("env-factory-test", Role::Operator);
1457 backend.save_key(&test_key).await.unwrap();
1458
1459 let keys = backend.load_keys().await.unwrap();
1460 assert_eq!(keys.len(), 1);
1461 assert!(keys.contains_key(&test_key.id));
1462
1463 std::env::remove_var(var_name);
1465 }
1466
1467 #[tokio::test]
1468 async fn test_create_file_storage_backend() {
1469 let temp_dir = TempDir::new().unwrap();
1470 let storage_path = temp_dir.path().join("factory_test_keys.enc");
1471
1472 let config = StorageConfig::File {
1473 path: storage_path.clone(),
1474 file_permissions: 0o600,
1475 dir_permissions: 0o700,
1476 require_secure_filesystem: false,
1477 enable_filesystem_monitoring: false,
1478 };
1479 let backend = create_storage_backend(&config).await.unwrap();
1480
1481 let test_key = create_test_key("file-factory-test", Role::Monitor);
1483 backend.save_key(&test_key).await.unwrap();
1484
1485 let keys = backend.load_keys().await.unwrap();
1486 assert_eq!(keys.len(), 1);
1487 assert!(keys.contains_key(&test_key.id));
1488
1489 assert!(storage_path.exists());
1491 }
1492
1493 #[tokio::test]
1494 async fn test_create_file_storage_backend_with_nested_path() {
1495 let temp_dir = TempDir::new().unwrap();
1496 let storage_path = temp_dir.path().join("nested").join("dirs").join("keys.enc");
1497
1498 let config = StorageConfig::File {
1499 path: storage_path.clone(),
1500 file_permissions: 0o600,
1501 dir_permissions: 0o700,
1502 require_secure_filesystem: false,
1503 enable_filesystem_monitoring: false,
1504 };
1505 let backend = create_storage_backend(&config).await.unwrap();
1506
1507 assert!(storage_path.parent().unwrap().exists());
1509
1510 let test_key = create_test_key(
1512 "nested-factory-test",
1513 Role::Device {
1514 allowed_devices: vec!["device1".to_string()],
1515 },
1516 );
1517 backend.save_key(&test_key).await.unwrap();
1518
1519 let keys = backend.load_keys().await.unwrap();
1520 assert_eq!(keys.len(), 1);
1521 assert!(keys.contains_key(&test_key.id));
1522 }
1523 }
1524
1525 #[tokio::test]
1526 async fn test_storage_backend_trait_object() {
1527 let memory_storage: Box<dyn StorageBackend> = Box::new(MemoryStorage::new());
1529 let env_storage: Box<dyn StorageBackend> =
1530 Box::new(EnvironmentStorage::new("TEST_TRAIT_OBJECT".to_string()));
1531
1532 let storages: Vec<Box<dyn StorageBackend>> = vec![memory_storage, env_storage];
1533
1534 for (i, storage) in storages.into_iter().enumerate() {
1535 let test_key = create_test_key(
1536 &format!("trait-test-{}", i),
1537 Role::Custom {
1538 permissions: vec!["test:read".to_string()],
1539 },
1540 );
1541
1542 storage.save_key(&test_key).await.unwrap();
1543 let keys = storage.load_keys().await.unwrap();
1544 assert_eq!(keys.len(), 1);
1545 assert!(keys.contains_key(&test_key.id));
1546 }
1547
1548 std::env::remove_var("TEST_TRAIT_OBJECT");
1550 }
1551
1552 #[tokio::test]
1553 async fn test_secure_api_key_conversion() {
1554 let original_key = create_test_key("conversion-test", Role::Admin);
1555 let secure_key = original_key.to_secure_storage();
1556 let restored_key = secure_key.to_api_key();
1557
1558 assert_eq!(restored_key.id, original_key.id);
1560 assert_eq!(restored_key.name, original_key.name);
1561 assert_eq!(restored_key.role, original_key.role);
1562 assert_eq!(restored_key.created_at, original_key.created_at);
1563 assert_eq!(restored_key.expires_at, original_key.expires_at);
1564 assert_eq!(restored_key.ip_whitelist, original_key.ip_whitelist);
1565 assert_eq!(restored_key.active, original_key.active);
1566 assert_eq!(restored_key.usage_count, original_key.usage_count);
1567
1568 assert_eq!(restored_key.key, "***redacted***");
1570 assert_ne!(restored_key.key, original_key.key);
1571
1572 assert_eq!(restored_key.secret_hash, original_key.secret_hash);
1574 assert_eq!(restored_key.salt, original_key.salt);
1575 }
1576}