Files
michaelschiemer/docs/deployment/secrets-management.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

615 lines
15 KiB
Markdown

# 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=<base64-encoded-key>
```
### 2. Configure Environment
Add to `.env.production`:
```env
# Vault Configuration
VAULT_ENCRYPTION_KEY=<base64-encoded-key-from-step-1>
```
**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=<new-base64-encoded-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=<generated-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: <strong-production-password>
docker exec php php console.php vault:set redis_password
# Enter: <redis-production-password>
# 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 <test-key>
```
## 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 <secret-key>
# 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`