180 lines
6.3 KiB
Rust
180 lines
6.3 KiB
Rust
|
|
//! 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(())
|
||
|
|
}
|