use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Instant;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SecretsError {
#[error("Secret not found: {0}")]
NotFound(String),
#[error("Environment variable not set: {0}")]
EnvNotSet(String),
#[error("Secret rotation failed: {0}")]
RotationFailed(String),
#[error("Encryption error: {0}")]
EncryptionError(String),
}
#[derive(Debug, Clone)]
struct SecretMetadata {
name: String,
created_at: Instant,
last_accessed: Instant,
last_rotated: Instant,
access_count: u64,
requires_rotation: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogEntry {
pub timestamp: String,
pub action: AuditAction,
pub secret_name: String,
pub result: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditAction {
Read,
Set,
Rotate,
Delete,
LoadFromEnv,
}
#[derive(Debug, Clone)]
pub struct SecretsManagerConfig {
pub audit_enabled: bool,
pub rotation_interval_secs: u64,
pub auto_rotation_check: bool,
}
impl Default for SecretsManagerConfig {
fn default() -> Self {
Self {
audit_enabled: true,
rotation_interval_secs: 86400 * 30, auto_rotation_check: true,
}
}
}
struct EncryptedStorage {
encrypted_secrets: RwLock<HashMap<String, Vec<u8>>>,
obfuscation_key: [u8; 32],
}
impl EncryptedStorage {
fn new() -> Self {
let obfuscation_key: [u8; 32] = {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let state = RandomState::new();
let mut hasher = state.build_hasher();
hasher.write_u64(std::process::id() as u64);
hasher.write_u64(Instant::now().elapsed().as_nanos() as u64);
let hash = hasher.finish();
let mut key = [0u8; 32];
for (i, byte) in key.iter_mut().enumerate() {
*byte = ((hash >> (i % 8 * 8)) & 0xFF) as u8;
}
key
};
Self {
encrypted_secrets: RwLock::new(HashMap::new()),
obfuscation_key,
}
}
fn encrypt(&self, plaintext: &str) -> Vec<u8> {
plaintext
.as_bytes()
.iter()
.enumerate()
.map(|(i, &byte)| byte ^ self.obfuscation_key[i % 32])
.collect()
}
fn decrypt(&self, ciphertext: &[u8]) -> Result<String, SecretsError> {
let decrypted: Vec<u8> = ciphertext
.iter()
.enumerate()
.map(|(i, &byte)| byte ^ self.obfuscation_key[i % 32])
.collect();
String::from_utf8(decrypted).map_err(|e| SecretsError::EncryptionError(e.to_string()))
}
fn set(&self, key: &str, value: &str) {
let encrypted = self.encrypt(value);
self.encrypted_secrets
.write()
.insert(key.to_string(), encrypted);
}
fn get(&self, key: &str) -> Result<Option<String>, SecretsError> {
let storage = self.encrypted_secrets.read();
match storage.get(key) {
Some(encrypted) => {
let decrypted = self.decrypt(encrypted)?;
Ok(Some(decrypted))
}
None => Ok(None),
}
}
fn remove(&self, key: &str) {
self.encrypted_secrets.write().remove(key);
}
fn contains(&self, key: &str) -> bool {
self.encrypted_secrets.read().contains_key(key)
}
fn keys(&self) -> Vec<String> {
self.encrypted_secrets.read().keys().cloned().collect()
}
}
pub struct SecretsManager {
storage: EncryptedStorage,
metadata: RwLock<HashMap<String, SecretMetadata>>,
audit_log: RwLock<Vec<AuditLogEntry>>,
config: SecretsManagerConfig,
}
impl SecretsManager {
pub fn new() -> Self {
Self::with_config(SecretsManagerConfig::default())
}
pub fn with_config(config: SecretsManagerConfig) -> Self {
Self {
storage: EncryptedStorage::new(),
metadata: RwLock::new(HashMap::new()),
audit_log: RwLock::new(Vec::new()),
config,
}
}
pub fn load_from_env(&self, key: &str, env_var: &str) -> Result<(), SecretsError> {
let value =
std::env::var(env_var).map_err(|_| SecretsError::EnvNotSet(env_var.to_string()))?;
self.set(key, &value)?;
self.log_audit(AuditAction::LoadFromEnv, key, "success")?;
Ok(())
}
pub fn set(&self, key: &str, value: &str) -> Result<(), SecretsError> {
let now = Instant::now();
self.storage.set(key, value);
let mut metadata = self.metadata.write();
metadata.insert(
key.to_string(),
SecretMetadata {
name: key.to_string(),
created_at: now,
last_accessed: now,
last_rotated: now,
access_count: 0,
requires_rotation: false,
},
);
self.log_audit(AuditAction::Set, key, "success")?;
Ok(())
}
pub fn get(&self, key: &str) -> Result<Option<String>, SecretsError> {
let result = self.storage.get(key)?;
if result.is_some() {
let mut metadata = self.metadata.write();
if let Some(meta) = metadata.get_mut(key) {
meta.last_accessed = Instant::now();
meta.access_count += 1;
if self.config.auto_rotation_check {
let elapsed = meta.last_rotated.elapsed().as_secs();
if elapsed >= self.config.rotation_interval_secs {
meta.requires_rotation = true;
}
}
}
}
self.log_audit(
AuditAction::Read,
key,
if result.is_some() {
"found"
} else {
"not_found"
},
)?;
Ok(result)
}
pub fn rotate(&self, key: &str, new_value: &str) -> Result<(), SecretsError> {
if !self.storage.contains(key) {
return Err(SecretsError::NotFound(key.to_string()));
}
self.storage.set(key, new_value);
let mut metadata = self.metadata.write();
if let Some(meta) = metadata.get_mut(key) {
meta.last_rotated = Instant::now();
meta.requires_rotation = false;
}
self.log_audit(AuditAction::Rotate, key, "success")?;
Ok(())
}
pub fn delete(&self, key: &str) -> Result<(), SecretsError> {
if !self.storage.contains(key) {
return Err(SecretsError::NotFound(key.to_string()));
}
self.storage.remove(key);
self.metadata.write().remove(key);
self.log_audit(AuditAction::Delete, key, "success")?;
Ok(())
}
pub fn contains(&self, key: &str) -> bool {
self.storage.contains(key)
}
pub fn get_keys_requiring_rotation(&self) -> Vec<String> {
self.metadata
.read()
.iter()
.filter(|(_, meta)| meta.requires_rotation)
.map(|(key, _)| key.clone())
.collect()
}
pub fn list_keys(&self) -> Vec<String> {
self.storage.keys()
}
pub fn get_metadata(&self, key: &str) -> Option<SecretMetadataInfo> {
self.metadata
.read()
.get(key)
.map(|meta| SecretMetadataInfo {
name: meta.name.clone(),
age_secs: meta.created_at.elapsed().as_secs(),
last_accessed_secs_ago: meta.last_accessed.elapsed().as_secs(),
last_rotated_secs_ago: meta.last_rotated.elapsed().as_secs(),
access_count: meta.access_count,
requires_rotation: meta.requires_rotation,
})
}
pub fn get_audit_log(&self) -> Vec<AuditLogEntry> {
self.audit_log.read().clone()
}
pub fn clear_audit_log(&self) {
self.audit_log.write().clear();
}
fn log_audit(
&self,
action: AuditAction,
secret_name: &str,
result: &str,
) -> Result<(), SecretsError> {
if !self.config.audit_enabled {
return Ok(());
}
let entry = AuditLogEntry {
timestamp: chrono::Utc::now().to_rfc3339(),
action,
secret_name: secret_name.to_string(),
result: result.to_string(),
};
self.audit_log.write().push(entry);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SecretMetadataInfo {
pub name: String,
pub age_secs: u64,
pub last_accessed_secs_ago: u64,
pub last_rotated_secs_ago: u64,
pub access_count: u64,
pub requires_rotation: bool,
}
impl Default for SecretsManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_get() {
let manager = SecretsManager::new();
manager.set("api_key", "secret123").unwrap();
let value = manager.get("api_key").unwrap();
assert_eq!(value, Some("secret123".to_string()));
}
#[test]
fn test_not_found() {
let manager = SecretsManager::new();
let value = manager.get("nonexistent").unwrap();
assert!(value.is_none());
}
#[test]
fn test_delete() {
let manager = SecretsManager::new();
manager.set("api_key", "secret123").unwrap();
assert!(manager.contains("api_key"));
manager.delete("api_key").unwrap();
assert!(!manager.contains("api_key"));
}
#[test]
fn test_rotate() {
let manager = SecretsManager::new();
manager.set("api_key", "secret123").unwrap();
manager.rotate("api_key", "new_secret456").unwrap();
let value = manager.get("api_key").unwrap();
assert_eq!(value, Some("new_secret456".to_string()));
}
#[test]
fn test_audit_log() {
let manager = SecretsManager::new();
manager.set("api_key", "secret123").unwrap();
manager.get("api_key").unwrap();
manager.rotate("api_key", "new_secret").unwrap();
let log = manager.get_audit_log();
assert_eq!(log.len(), 3);
assert!(matches!(log[0].action, AuditAction::Set));
assert!(matches!(log[1].action, AuditAction::Read));
assert!(matches!(log[2].action, AuditAction::Rotate));
}
#[test]
fn test_access_count() {
let manager = SecretsManager::new();
manager.set("api_key", "secret123").unwrap();
for _ in 0..5 {
manager.get("api_key").unwrap();
}
let meta = manager.get_metadata("api_key").unwrap();
assert_eq!(meta.access_count, 5);
}
#[test]
fn test_encrypted_storage() {
let storage = EncryptedStorage::new();
storage.set("test_key", "test_value");
let value = storage.get("test_key").unwrap();
assert_eq!(value, Some("test_value".to_string()));
}
#[test]
fn test_encryption_roundtrip() {
let storage = EncryptedStorage::new();
let plaintext = "my_secret_api_key_12345";
storage.set("key", plaintext);
let decrypted = storage.get("key").unwrap().unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_list_keys() {
let manager = SecretsManager::new();
manager.set("key1", "value1").unwrap();
manager.set("key2", "value2").unwrap();
manager.set("key3", "value3").unwrap();
let keys = manager.list_keys();
assert_eq!(keys.len(), 3);
assert!(keys.contains(&"key1".to_string()));
assert!(keys.contains(&"key2".to_string()));
assert!(keys.contains(&"key3".to_string()));
}
#[test]
fn test_rotation_required() {
let config = SecretsManagerConfig {
audit_enabled: true,
rotation_interval_secs: 0, auto_rotation_check: true,
};
let manager = SecretsManager::with_config(config);
manager.set("api_key", "secret").unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
manager.get("api_key").unwrap();
let keys = manager.get_keys_requiring_rotation();
assert!(!keys.is_empty());
assert!(keys.contains(&"api_key".to_string()));
}
#[test]
fn test_load_from_env() {
std::env::set_var("TEST_SECRET_KEY", "test_env_value");
let manager = SecretsManager::new();
manager.load_from_env("my_key", "TEST_SECRET_KEY").unwrap();
let value = manager.get("my_key").unwrap();
assert_eq!(value, Some("test_env_value".to_string()));
std::env::remove_var("TEST_SECRET_KEY");
}
#[test]
fn test_load_from_missing_env() {
let manager = SecretsManager::new();
let result = manager.load_from_env("key", "NONEXISTENT_ENV_VAR_12345");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SecretsError::EnvNotSet(_)));
}
#[test]
fn test_clear_audit_log() {
let manager = SecretsManager::new();
manager.set("key", "value").unwrap();
assert!(!manager.get_audit_log().is_empty());
manager.clear_audit_log();
assert!(manager.get_audit_log().is_empty());
}
#[test]
fn test_delete_nonexistent() {
let manager = SecretsManager::new();
let result = manager.delete("nonexistent");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SecretsError::NotFound(_)));
}
#[test]
fn test_rotate_nonexistent() {
let manager = SecretsManager::new();
let result = manager.rotate("nonexistent", "new_value");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SecretsError::NotFound(_)));
}
#[test]
fn test_empty_key_name() {
let manager = SecretsManager::new();
let result = manager.set("", "value");
assert!(result.is_ok());
assert!(manager.contains(""));
}
#[test]
fn test_empty_secret_value() {
let manager = SecretsManager::new();
let result = manager.set("key", "");
assert!(result.is_ok());
let value = manager.get("key").unwrap();
assert_eq!(value, Some("".to_string()));
}
#[test]
fn test_long_key_name() {
let manager = SecretsManager::new();
let long_key = "a".repeat(10000);
let result = manager.set(&long_key, "value");
assert!(result.is_ok());
assert!(manager.contains(&long_key));
}
#[test]
fn test_long_secret_value() {
let manager = SecretsManager::new();
let long_value = "x".repeat(1_000_000); let result = manager.set("big_secret", &long_value);
assert!(result.is_ok());
let value = manager.get("big_secret").unwrap();
assert_eq!(value, Some(long_value));
}
#[test]
fn test_unicode_key_and_value() {
let manager = SecretsManager::new();
let unicode_key = "密钥_🔑_key";
let unicode_value = "秘密值_🔐_value";
manager.set(unicode_key, unicode_value).unwrap();
let value = manager.get(unicode_key).unwrap();
assert_eq!(value, Some(unicode_value.to_string()));
}
#[test]
fn test_special_characters_in_value() {
let manager = SecretsManager::new();
let special_value = "!@#$%^&*()_+-=[]{}|;':\",./<>?\n\t\r\\";
manager.set("special", special_value).unwrap();
let value = manager.get("special").unwrap();
assert_eq!(value, Some(special_value.to_string()));
}
#[test]
fn test_binary_like_value() {
let manager = SecretsManager::new();
let binary_value: String = (0u8..=255).map(|b| b as char).collect();
manager.set("binary", &binary_value).unwrap();
let value = manager.get("binary").unwrap();
assert_eq!(value, Some(binary_value));
}
#[test]
fn test_get_metadata_for_nonexistent_key() {
let manager = SecretsManager::new();
let meta = manager.get_metadata("nonexistent");
assert!(meta.is_none());
}
#[test]
fn test_audit_disabled() {
let config = SecretsManagerConfig {
audit_enabled: false,
rotation_interval_secs: 86400,
auto_rotation_check: false,
};
let manager = SecretsManager::with_config(config);
manager.set("key", "value").unwrap();
manager.get("key").unwrap();
assert!(manager.get_audit_log().is_empty());
}
#[test]
fn test_auto_rotation_disabled() {
let config = SecretsManagerConfig {
audit_enabled: true,
rotation_interval_secs: 0,
auto_rotation_check: false,
};
let manager = SecretsManager::with_config(config);
manager.set("key", "value").unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
manager.get("key").unwrap();
let keys = manager.get_keys_requiring_rotation();
assert!(keys.is_empty());
}
#[test]
fn test_overwrite_existing_key() {
let manager = SecretsManager::new();
manager.set("key", "value1").unwrap();
assert_eq!(manager.get("key").unwrap(), Some("value1".to_string()));
manager.set("key", "value2").unwrap();
assert_eq!(manager.get("key").unwrap(), Some("value2".to_string()));
let log = manager.get_audit_log();
let set_count = log
.iter()
.filter(|e| matches!(e.action, AuditAction::Set))
.count();
assert_eq!(set_count, 2);
}
#[test]
fn test_concurrent_access() {
use std::sync::Arc;
use std::thread;
let manager = Arc::new(SecretsManager::new());
let mut handles = vec![];
for i in 0..10 {
let m = Arc::clone(&manager);
handles.push(thread::spawn(move || {
let key = format!("key_{}", i);
m.set(&key, &format!("value_{}", i)).unwrap();
m.get(&key).unwrap();
}));
}
for h in handles {
h.join().unwrap();
}
for i in 0..10 {
let key = format!("key_{}", i);
assert!(manager.contains(&key));
}
}
#[test]
fn test_encrypted_storage_invalid_utf8() {
let storage = EncryptedStorage::new();
storage.set("key", "value");
let encrypted = storage
.encrypted_secrets
.read()
.get("key")
.cloned()
.unwrap();
let mut corrupted = encrypted.clone();
for i in 0..corrupted.len().min(10) {
corrupted[i] = 0x80 | (corrupted[i] & 0x7F);
}
storage
.encrypted_secrets
.write()
.insert("key".to_string(), corrupted);
let result = storage.get("key");
assert!(result.is_ok() || result.is_err());
}
}