// Encrypted secrets management for containers // Stores secrets securely and injects them at runtime use anyhow::{Context, Result}; use std::collections::HashMap; use std::path::PathBuf; use tokio::fs; use uuid::Uuid; pub struct SecretsManager { secrets_dir: PathBuf, encryption_key: Vec, // In production, derive from user password } impl SecretsManager { pub fn new(secrets_dir: PathBuf, encryption_key: Vec) -> Self { Self { secrets_dir, encryption_key, } } /// Store a secret for an app pub async fn store_secret( &self, app_id: &str, key: &str, value: &str, ) -> Result { let secret_id = Uuid::new_v4().to_string(); let secret_path = self.secrets_dir .join(app_id) .join(format!("{}.secret", secret_id)); fs::create_dir_all(secret_path.parent().unwrap()).await?; // TODO: Encrypt the secret value // For now, store as plaintext (MUST be encrypted in production) fs::write(&secret_path, value).await .context("Failed to write secret")?; // Set restrictive permissions #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&secret_path).await?.permissions(); perms.set_mode(0o600); fs::set_permissions(&secret_path, perms).await?; } Ok(secret_id) } /// Retrieve a secret (returns the secret ID path for volume mounting) pub fn get_secret_path(&self, app_id: &str, secret_id: &str) -> PathBuf { self.secrets_dir .join(app_id) .join(format!("{}.secret", secret_id)) } /// List secrets for an app pub async fn list_secrets(&self, app_id: &str) -> Result> { let app_secrets_dir = self.secrets_dir.join(app_id); if !app_secrets_dir.exists() { return Ok(vec![]); } let mut secrets = Vec::new(); let mut entries = fs::read_dir(&app_secrets_dir).await?; while let Some(entry) = entries.next_entry().await? { let path = entry.path(); if path.extension().and_then(|s| s.to_str()) == Some("secret") { if let Some(secret_id) = path.file_stem() .and_then(|s| s.to_str()) .map(|s| s.to_string()) { secrets.push(secret_id); } } } Ok(secrets) } /// Delete a secret pub async fn delete_secret(&self, app_id: &str, secret_id: &str) -> Result<()> { let secret_path = self.secrets_dir .join(app_id) .join(format!("{}.secret", secret_id)); if secret_path.exists() { fs::remove_file(&secret_path).await?; } Ok(()) } }