- 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.
615 lines
15 KiB
Markdown
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`
|