# Secrets Management with Vault Comprehensive documentation for secure secrets management using the framework's Vault system for production deployment. ## Overview The Custom PHP Framework includes a fully-featured Vault system for secure secrets storage with: - **Libsodium Authenticated Encryption** (XSalsa20-Poly1305) - **Database-backed Storage** with encrypted values - **Audit Logging** for all operations (read, write, delete, rotate) - **Key Rotation Support** for security compliance - **CLI Commands** for production management ## Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Application │───▶│ Vault Service │───▶│ PostgreSQL │ │ (Get/Set) │ │ (Encryption) │ │ (Encrypted) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ▼ ▼ │ ┌─────────────────┐ ┌─────────────────┐ └───────────▶│ Audit Logger │───▶│ vault_audit │ │ (All Access) │ │ (Audit Log) │ └─────────────────┘ └─────────────────┘ ``` ## Security Features ### 1. Authenticated Encryption - **Algorithm**: Libsodium `sodium_crypto_secretbox` (XSalsa20-Poly1305) - **Key Size**: 256-bit (32 bytes) - **Nonce**: 192-bit (24 bytes), unique per encryption - **MAC**: Poly1305 authentication tag - **Benefits**: Encryption + Authentication in one operation, tamper-proof ### 2. Audit Logging Every Vault operation is logged: - **Actions**: READ, WRITE, DELETE, ROTATE - **Metadata**: Timestamp, IP Address, User Agent, User ID - **Status**: Success/Failure with error messages - **Usage Tracking**: Access count, last accessed timestamp ### 3. Secure Key Management - **Generation**: Cryptographically secure via `sodium_crypto_secretbox_keygen()` - **Storage**: `.env.production` file (NOT committed to git) - **Rotation**: Re-encrypt all secrets with new key - **Backup**: Old keys retained until rotation verified ## Installation & Setup ### 1. Generate Encryption Key ```bash # Generate new Vault encryption key docker exec php php console.php vault:generate-key # Output: # 🔐 Generated Vault Encryption Key: # VAULT_ENCRYPTION_KEY= ``` ### 2. Configure Environment Add to `.env.production`: ```env # Vault Configuration VAULT_ENCRYPTION_KEY= ``` **CRITICAL**: - NEVER commit `.env.production` to version control - Store encryption key in secure password manager as backup - Losing key = losing ALL secrets permanently ### 3. Create Vault Tables ```bash # Run Vault migration docker exec php php console.php db:migrate # Verifies: # - vault_secrets table created # - vault_audit table created ``` ### 4. Test Vault ```bash # Store test secret docker exec php php console.php vault:set test_key test_value # Retrieve test secret docker exec php php console.php vault:get test_key # Output should show: test_value ``` ## CLI Commands ### `vault:set` - Store Secret ```bash # With value in command docker exec php php console.php vault:set api_key "sk_live_abc123" # Interactive (password prompt - more secure) docker exec php php console.php vault:set api_key # Prompt: Enter secret value: **** ``` **Output**: ``` ✅ Secret 'api_key' stored successfully in vault ``` ### `vault:get` - Retrieve Secret ```bash docker exec php php console.php vault:get api_key ``` **Output**: ``` ✅ Secret 'api_key' retrieved: sk_live_abc123 🔒 This value should be kept secure! ``` ### `vault:list` - List All Secrets ```bash docker exec php php console.php vault:list ``` **Output**: ``` 🔐 Vault Secrets: • api_key Accessed: 5 times, Last: 2024-01-15 14:32:45 • database_password Accessed: 12 times, Last: 2024-01-15 14:30:12 • oauth_client_secret Accessed: Never Total: 3 secrets ``` ### `vault:delete` - Delete Secret ```bash docker exec php php console.php vault:delete old_api_key ``` **Output**: ``` ⚠️ Delete secret 'old_api_key'? (yes/no): yes ✅ Secret 'old_api_key' deleted successfully ``` ### `vault:audit` - View Audit Log ```bash # Show last 50 audit entries (default) docker exec php php console.php vault:audit # Show last 100 entries docker exec php php console.php vault:audit 100 # Show audit log for specific secret docker exec php php console.php vault:audit 50 api_key ``` **Output**: ``` 📊 Vault Audit Log: ✓ [2024-01-15 14:32:45] read - api_key IP: 203.0.113.42 ✓ [2024-01-15 14:30:12] write - database_password User: admin, IP: 203.0.113.42 ✗ [2024-01-15 14:28:01] read - missing_key IP: 203.0.113.42 Error: Secret not found Showing 3 entries ``` ### `vault:rotate-key` - Rotate Encryption Key ```bash docker exec php php console.php vault:rotate-key ``` **Output**: ``` ⚠️ KEY ROTATION - CRITICAL OPERATION This will re-encrypt all secrets with a new key. Make sure you have a backup before proceeding! Continue with key rotation? (yes/no): yes 🔄 Rotating encryption key... ✅ Successfully rotated 15 secrets New encryption key: VAULT_ENCRYPTION_KEY= 🚨 IMPORTANT: • Update VAULT_ENCRYPTION_KEY in your .env file immediately • Restart your application to use the new key • Keep the old key safe until rotation is verified ``` ## Application Integration ### Basic Usage ```php use App\Framework\Vault\Vault; use App\Framework\Vault\ValueObjects\SecretKey; use App\Framework\Vault\ValueObjects\SecretValue; final readonly class PaymentService { public function __construct( private Vault $vault ) {} public function processPayment(Order $order): PaymentResult { // Retrieve Stripe API key from Vault $apiKey = $this->vault->get(SecretKey::from('stripe_api_key')); // Use secret (revealed only when needed) $stripe = new StripeClient($apiKey->reveal()); // Process payment return $stripe->charge($order->getTotal()); } } ``` ### Storing Secrets ```php // Store new secret $this->vault->set( SecretKey::from('shopify_access_token'), new SecretValue('shpat_abc123xyz...') ); // Update existing secret (same method) $this->vault->set( SecretKey::from('shopify_access_token'), new SecretValue('shpat_new_token...') ); ``` ### Checking Secret Existence ```php if ($this->vault->has(SecretKey::from('api_key'))) { // Secret exists $apiKey = $this->vault->get(SecretKey::from('api_key')); } else { // Secret missing throw new ConfigurationException('API key not configured in Vault'); } ``` ### Deleting Secrets ```php // Delete deprecated secret $this->vault->delete(SecretKey::from('old_api_key')); ``` ### Metadata & Audit ```php $metadata = $this->vault->getMetadata(SecretKey::from('api_key')); // Metadata includes: // - createdAt: DateTimeImmutable // - updatedAt: DateTimeImmutable // - accessCount: int // - lastAccessedAt: ?DateTimeImmutable // - createdBy: ?string // - updatedBy: ?string echo "Secret accessed {$metadata->accessCount} times"; echo "Last access: {$metadata->lastAccessedAt->format('Y-m-d H:i:s')}"; ``` ## Production Deployment Workflow ### Initial Production Setup ```bash # 1. Generate encryption key docker exec php php console.php vault:generate-key # 2. Add to .env.production echo "VAULT_ENCRYPTION_KEY=" >> .env.production chmod 600 .env.production # 3. Run migrations docker exec php php console.php db:migrate # 4. Store production secrets docker exec php php console.php vault:set stripe_api_key # Enter: sk_live_... docker exec php php console.php vault:set database_password # Enter: docker exec php php console.php vault:set redis_password # Enter: # 5. Verify secrets stored docker exec php php console.php vault:list ``` ### Migration from .env to Vault ```bash # Script to migrate secrets from .env to Vault #!/bin/bash # Secrets to migrate (add your secrets here) SECRETS=( "STRIPE_API_KEY" "SHOPIFY_ACCESS_TOKEN" "RAPIDMAIL_PASSWORD" "OAUTH_CLIENT_SECRET" ) for secret in "${SECRETS[@]}"; do # Get value from .env VALUE=$(grep "^${secret}=" .env.production | cut -d '=' -f2-) if [ -n "$VALUE" ]; then echo "Migrating ${secret}..." # Store in Vault docker exec php php console.php vault:set "${secret}" "${VALUE}" # Comment out in .env (keep for reference) sed -i "s/^${secret}=/# MIGRATED_TO_VAULT: ${secret}=/" .env.production fi done echo "✅ Migration complete" echo "🔒 Update application to use Vault::get() instead of Environment::get()" ``` ### Key Rotation Schedule **Recommended Schedule**: - **Quarterly**: Rotate Vault encryption key - **Annually**: Rotate all application secrets (API keys, passwords) - **On Demand**: Rotate if key compromise suspected **Rotation Procedure**: ```bash # 1. Backup current Vault docker exec db pg_dump -U postgres michaelschiemer_prod -t vault_secrets > vault_backup_$(date +%Y%m%d).sql # 2. Rotate encryption key docker exec php php console.php vault:rotate-key # 3. Update .env.production with new key # (shown in command output) # 4. Restart application docker-compose -f docker-compose.yml \ -f docker-compose.production.yml \ --env-file .env.production \ restart php # 5. Verify all secrets accessible docker exec php php console.php vault:list # 6. Test application functionality curl https://your-domain.com/health ``` ## Security Best Practices ### 1. Key Management **DO**: - ✅ Generate key with `vault:generate-key` command - ✅ Store key in `.env.production` (not committed) - ✅ Backup key in secure password manager - ✅ Use different keys per environment (dev, staging, prod) - ✅ Rotate keys quarterly **DON'T**: - ❌ Commit `.env.production` to version control - ❌ Share keys via email or Slack - ❌ Use same key across environments - ❌ Store key in plain text files - ❌ Log decrypted secret values ### 2. Secret Storage **DO**: - ✅ Store ALL sensitive credentials in Vault - ✅ Use descriptive secret keys (e.g., `stripe_live_api_key`) - ✅ Document which secrets are required - ✅ Use Vault for OAuth tokens, API keys, passwords - ✅ Set secrets via CLI (not in code) **DON'T**: - ❌ Store secrets in `.env` files - ❌ Hardcode secrets in application code - ❌ Store secrets in database without Vault - ❌ Share secrets between environments - ❌ Use weak or guessable secret values ### 3. Access Control **DO**: - ✅ Monitor audit log regularly (`vault:audit`) - ✅ Review secret access patterns - ✅ Investigate failed access attempts - ✅ Track who accessed which secrets - ✅ Revoke secrets on employee offboarding **DON'T**: - ❌ Ignore audit log warnings - ❌ Share Vault CLI access broadly - ❌ Allow anonymous secret access - ❌ Disable audit logging - ❌ Reuse secrets across services ### 4. Disaster Recovery **Backup Strategy**: ```bash # Weekly backup of Vault tables 0 2 * * 0 docker exec db pg_dump -U postgres michaelschiemer_prod -t vault_secrets -t vault_audit | gzip > /mnt/backups/vault_$(date +\%Y\%m\%d).sql.gz ``` **Recovery Procedure**: ```bash # 1. Restore Vault tables from backup gunzip -c vault_20240115.sql.gz | docker exec -i db psql -U postgres michaelschiemer_prod # 2. Verify encryption key in .env.production grep VAULT_ENCRYPTION_KEY .env.production # 3. Test Vault access docker exec php php console.php vault:list # 4. Verify secret retrieval docker exec php php console.php vault:get ``` ## Monitoring & Alerting ### Health Checks ```php // Include in /health endpoint final readonly class VaultHealthCheck { public function check(): HealthCheckResult { try { // Test Vault connectivity $testKey = SecretKey::from('_health_check_test'); if ($this->vault->has($testKey)) { return HealthCheckResult::healthy('Vault'); } // Create test secret if not exists $this->vault->set($testKey, new SecretValue('healthy')); return HealthCheckResult::healthy('Vault'); } catch (\Throwable $e) { return HealthCheckResult::unhealthy('Vault', $e->getMessage()); } } } ``` ### Audit Log Monitoring ```bash # Monitor failed access attempts docker exec php php console.php vault:audit 100 | grep "✗" # Alert on suspicious patterns # - Multiple failed reads for same key # - Access from unusual IPs # - High-frequency access attempts ``` ### Metrics Collection ```php // Collect Vault metrics final readonly class VaultMetricsCollector { public function collect(): array { return [ 'total_secrets' => count($this->vault->all()), 'failed_access_24h' => $this->auditLogger->countFailedAccess(Duration::fromHours(24)), 'most_accessed_secrets' => $this->auditLogger->getMostAccessedSecrets(10), 'last_rotation' => $this->getLastRotationTimestamp(), ]; } } ``` ## Troubleshooting ### Problem: Vault Not Available **Symptoms**: ``` ❌ Vault not available. Make sure VAULT_ENCRYPTION_KEY is set in .env ``` **Solution**: ```bash # 1. Check .env.production grep VAULT_ENCRYPTION_KEY .env.production # 2. Generate key if missing docker exec php php console.php vault:generate-key # 3. Add to .env.production and restart docker-compose -f docker-compose.yml \ -f docker-compose.production.yml \ restart php ``` ### Problem: Decryption Failed **Symptoms**: ``` ❌ Failed to retrieve secret: Decryption failed ``` **Causes**: - Wrong encryption key in `.env.production` - Corrupted database entry - Key was rotated but `.env` not updated **Solution**: ```bash # 1. Verify encryption key matches what was used during encryption # 2. Check vault_audit for rotation events docker exec php php console.php vault:audit 100 | grep ROTATE # 3. If key mismatch, restore correct key or rotate all secrets ``` ### Problem: Secret Not Found **Symptoms**: ``` ❌ Secret 'api_key' not found in vault ``` **Solution**: ```bash # 1. List all secrets docker exec php php console.php vault:list # 2. Check audit log for deletions docker exec php php console.php vault:audit 100 api_key # 3. Re-create secret docker exec php php console.php vault:set api_key ``` ### Problem: High Access Count **Symptoms**: Secret accessed thousands of times per hour **Investigation**: ```bash # 1. Check metadata docker exec php php console.php vault:list # 2. Review audit log docker exec php php console.php vault:audit 1000 # 3. Identify access pattern (IP, User Agent) ``` **Solution**: - Cache secret value in application memory (careful!) - Review application code for inefficient Vault access - Consider moving to environment variable if accessed frequently ## See Also - **Environment Configuration**: `docs/deployment/env-production-template.md` - **Production Prerequisites**: `docs/deployment/production-prerequisites.md` - **Database Patterns**: `docs/claude/database-patterns.md` - **Security Patterns**: `docs/claude/security-patterns.md`