provisioning-platform/prov-ecosystem/crates/encrypt/examples/config_encryption.rs

180 lines
6.3 KiB
Rust
Raw Permalink Normal View History

//! Configuration file encryption example.
//!
//! Demonstrates a practical use case: encrypting sensitive fields in
//! configuration files using different backends.
//!
//! # Typical Workflow
//!
//! 1. Developer has plaintext config with secrets
//! 2. Run encryption to hide sensitive values
//! 3. Store encrypted config in version control
//! 4. At runtime, decrypt using available backend
//! 5. Different environments use different backends (Age for dev, KMS for prod)
//!
//! # Usage
//!
//! ```bash
//! cargo run --example config_encryption --features age,sops
//! ```
use encrypt::{decrypt, encrypt, BackendSpec};
#[derive(Debug, Clone)]
struct AppConfig {
name: String,
db_host: String,
db_password: String,
api_key: String,
debug: bool,
}
impl AppConfig {
fn example() -> Self {
Self {
name: "MyApp".to_string(),
db_host: "postgres.example.com".to_string(),
db_password: "super-secret-postgres-pwd-123".to_string(),
api_key: "sk-prod-1234567890abcdef".to_string(),
debug: false,
}
}
fn display_plaintext(&self) {
println!("Configuration (PLAINTEXT):");
println!(" name: {}", self.name);
println!(" db_host: {}", self.db_host);
println!(" db_password: {}", self.db_password);
println!(" api_key: {}", self.api_key);
println!(" debug: {}\n", self.debug);
}
fn display_encrypted(&self, backend_name: &str) {
println!("Configuration (ENCRYPTED with {}):", backend_name);
println!(" name: {}", self.name);
println!(" db_host: {}", self.db_host);
println!(" db_password: [ENCRYPTED]");
println!(" api_key: [ENCRYPTED]");
println!(" debug: {}\n", self.debug);
}
}
fn encrypt_config(
config: &AppConfig,
spec: &BackendSpec,
) -> Result<(String, String), Box<dyn std::error::Error>> {
let encrypted_password = encrypt(&config.db_password, spec)?;
let encrypted_api_key = encrypt(&config.api_key, spec)?;
Ok((encrypted_password, encrypted_api_key))
}
fn decrypt_secrets(
encrypted_password: &str,
encrypted_api_key: &str,
spec: &BackendSpec,
) -> Result<(String, String), Box<dyn std::error::Error>> {
let password = decrypt(encrypted_password, spec)?;
let api_key = decrypt(encrypted_api_key, spec)?;
Ok((password, api_key))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Configuration Encryption Example ===\n");
let config = AppConfig::example();
// Step 1: Show plaintext config
println!("Step 1: Original Configuration");
println!("================================");
config.display_plaintext();
// Step 2: Encrypt with Age backend
println!("Step 2: Encrypt with Age Backend");
println!("=================================");
let age_spec = BackendSpec::age_default();
match encrypt_config(&config, &age_spec) {
Ok((encrypted_password, encrypted_api_key)) => {
println!("✓ Encrypted successfully\n");
// Show config with encrypted values
config.display_encrypted("Age");
// Step 3: Simulate storing in version control
println!("Step 3: Store in Version Control");
println!("=================================");
println!(
"Encrypted password (first 60 chars): {}...",
&encrypted_password[..60.min(encrypted_password.len())]
);
println!(
"Encrypted API key (first 60 chars): {}...\n",
&encrypted_api_key[..60.min(encrypted_api_key.len())]
);
// Step 4: Decrypt for runtime use
println!("Step 4: Decrypt at Runtime");
println!("==========================");
match decrypt_secrets(&encrypted_password, &encrypted_api_key, &age_spec) {
Ok((decrypted_password, decrypted_api_key)) => {
println!("✓ Decrypted successfully\n");
// Verify values match
if decrypted_password == config.db_password
&& decrypted_api_key == config.api_key
{
println!("✓ All secrets recovered correctly!");
println!(" - Database password matches");
println!(" - API key matches");
} else {
println!("✗ Secret mismatch!");
return Err("Decryption verification failed".into());
}
}
Err(e) => {
println!("✗ Decryption failed: {}\n", e);
return Err(e);
}
}
}
Err(e) => {
println!("✗ Encryption failed: {}\n", e);
println!("Ensure Age key exists: age-keygen -o ~/.age/key.txt");
return Err(e);
}
}
// Step 5: Show multi-environment example
println!("\nStep 5: Multi-Environment Example");
println!("==================================");
println!("\nDevelopment (Age - local):");
let dev_spec = BackendSpec::age_default();
println!(" Backend: {}", dev_spec.backend_name());
println!(" Location: ~/.age/key.txt");
println!(" Use case: Local development, no KMS needed\n");
println!("Staging (SOPS - AWS KMS):");
let staging_spec = BackendSpec::sops();
println!(" Backend: {}", staging_spec.backend_name());
println!(" Location: .sops.yaml → AWS KMS");
println!(" Use case: Team environment with key management\n");
println!("Production (SecretumVault - post-quantum):");
let prod_spec = BackendSpec::secretumvault("https://vault.prod:8200", "token", "app-key");
println!(" Backend: {}", prod_spec.backend_name());
println!(" Location: SecretumVault HTTP API");
println!(" Use case: Enterprise with post-quantum crypto requirements\n");
println!("Configuration Management Best Practices:");
println!(" 1. Never commit plaintext secrets to Git");
println!(" 2. Use different backends per environment");
println!(" 3. Rotate keys regularly");
println!(" 4. Log who accessed which secrets");
println!(" 5. Use encryption context for audit trails");
println!(" 6. Implement secret versioning");
Ok(())
}