feat: Implement dual-stage database migration with lazy field encryption
Phase 1: Database file migration (startup) - Add DatabaseMigration class for safe unencrypted → encrypted DB migration - Disable foreign key constraints during migration to prevent constraint failures - Create timestamped backups and verification checks - Rename original files instead of deletion for safety Phase 2: Lazy field encryption (user login) - Add LazyFieldEncryption utility for plaintext field detection - Implement gradual migration of sensitive fields using user KEK - Update DataCrypto to handle mixed plaintext/encrypted data - Integrate lazy encryption into AuthManager login flow Key improvements: - Non-destructive migration with comprehensive backup strategy - Automatic detection and handling of plaintext vs encrypted fields - User-transparent migration during normal login process - Complete migration logging and admin API endpoints - Foreign key constraint handling during database structure migration Resolves data decryption errors during Docker updates by providing seamless transition from plaintext to encrypted storage. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
323
MIGRATION-TESTING.md
Normal file
323
MIGRATION-TESTING.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# Database Migration Testing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the testing procedures for the automatic database migration system that migrates unencrypted SQLite databases to encrypted format during Docker deployment updates.
|
||||
|
||||
## Migration System Features
|
||||
|
||||
✅ **Automatic Detection**: Detects unencrypted databases on startup
|
||||
✅ **Safe Backup**: Creates timestamped backups before migration
|
||||
✅ **Integrity Verification**: Validates migration completeness
|
||||
✅ **Non-destructive**: Original files are renamed, not deleted
|
||||
✅ **Cleanup**: Removes old backup files (keeps latest 3)
|
||||
✅ **Admin API**: Migration status and history endpoints
|
||||
✅ **Detailed Logging**: Comprehensive migration logs
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Scenario 1: Fresh Installation (No Migration Needed)
|
||||
**Setup**: Clean Docker container with no existing database files
|
||||
**Expected**:
|
||||
- New encrypted database created
|
||||
- No migration messages in logs
|
||||
- Status API shows "Fresh installation detected"
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# Clean start
|
||||
docker run --rm termix:latest
|
||||
# Check logs for "fresh installation"
|
||||
# GET /database/migration/status should show needsMigration: false
|
||||
```
|
||||
|
||||
### Scenario 2: Standard Migration (Unencrypted → Encrypted)
|
||||
**Setup**: Existing unencrypted `db.sqlite` file with user data
|
||||
**Expected**:
|
||||
- Automatic migration on startup
|
||||
- Backup file created (`.migration-backup-{timestamp}`)
|
||||
- Original file renamed (`.migrated-{timestamp}`)
|
||||
- Encrypted database created successfully
|
||||
- All data preserved and accessible
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# 1. Create test data in unencrypted format
|
||||
docker run -v /host/data:/app/data termix:old-version
|
||||
# Add some SSH hosts and credentials via UI
|
||||
|
||||
# 2. Stop container and update to new version
|
||||
docker stop container_id
|
||||
docker run -v /host/data:/app/data termix:latest
|
||||
|
||||
# 3. Check migration logs
|
||||
docker logs container_id | grep -i migration
|
||||
|
||||
# 4. Verify data integrity via API
|
||||
curl -H "Authorization: Bearer $TOKEN" http://localhost:8081/database/migration/status
|
||||
```
|
||||
|
||||
### Scenario 3: Already Encrypted (No Migration Needed)
|
||||
**Setup**: Only encrypted database file exists
|
||||
**Expected**:
|
||||
- No migration performed
|
||||
- Database loads normally
|
||||
- Status API shows "Only encrypted database exists"
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# Start with existing encrypted database
|
||||
docker run -v /host/encrypted-data:/app/data termix:latest
|
||||
# Verify no migration messages in logs
|
||||
```
|
||||
|
||||
### Scenario 4: Both Files Exist (Safety Mode)
|
||||
**Setup**: Both encrypted and unencrypted databases present
|
||||
**Expected**:
|
||||
- Migration skipped for safety
|
||||
- Warning logged about manual intervention
|
||||
- Both files preserved
|
||||
- Uses encrypted database
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# Manually create both files
|
||||
touch /host/data/db.sqlite
|
||||
touch /host/data/db.sqlite.encrypted
|
||||
docker run -v /host/data:/app/data termix:latest
|
||||
# Check for safety warning in logs
|
||||
```
|
||||
|
||||
### Scenario 5: Migration Failure Recovery
|
||||
**Setup**: Simulate migration failure (corrupted source file)
|
||||
**Expected**:
|
||||
- Migration fails safely
|
||||
- Backup file preserved
|
||||
- Original unencrypted file untouched
|
||||
- Clear error message with recovery instructions
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# Create corrupted database file
|
||||
echo "corrupted" > /host/data/db.sqlite
|
||||
docker run -v /host/data:/app/data termix:latest
|
||||
# Verify error handling and backup preservation
|
||||
```
|
||||
|
||||
### Scenario 6: Large Database Migration
|
||||
**Setup**: Large unencrypted database (>100MB with many records)
|
||||
**Expected**:
|
||||
- Migration completes successfully
|
||||
- Performance is acceptable (under 30 seconds)
|
||||
- Memory usage stays reasonable
|
||||
- All data integrity checks pass
|
||||
|
||||
**Test Commands**:
|
||||
```bash
|
||||
# Create large dataset first
|
||||
# Monitor migration duration and memory usage
|
||||
docker stats container_id
|
||||
```
|
||||
|
||||
## API Testing
|
||||
|
||||
### Migration Status Endpoint
|
||||
```bash
|
||||
# Admin access required
|
||||
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
http://localhost:8081/database/migration/status
|
||||
|
||||
# Expected response:
|
||||
{
|
||||
"migrationStatus": {
|
||||
"needsMigration": false,
|
||||
"hasUnencryptedDb": false,
|
||||
"hasEncryptedDb": true,
|
||||
"unencryptedDbSize": 0,
|
||||
"reason": "Only encrypted database exists. No migration needed."
|
||||
},
|
||||
"files": {
|
||||
"unencryptedDbSize": 0,
|
||||
"encryptedDbSize": 524288,
|
||||
"backupFiles": 2,
|
||||
"migratedFiles": 1
|
||||
},
|
||||
"recommendations": [
|
||||
"Database is properly encrypted",
|
||||
"No action required"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Migration History Endpoint
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
http://localhost:8081/database/migration/history
|
||||
|
||||
# Expected response:
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"name": "db.sqlite.migration-backup-2024-09-24T10-30-00-000Z",
|
||||
"size": 262144,
|
||||
"created": "2024-09-24T10:30:00.000Z",
|
||||
"modified": "2024-09-24T10:30:00.000Z",
|
||||
"type": "backup"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"totalBackups": 1,
|
||||
"totalMigrated": 1,
|
||||
"oldestBackup": "2024-09-24T10:30:00.000Z",
|
||||
"newestBackup": "2024-09-24T10:30:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Log Analysis
|
||||
|
||||
### Successful Migration Logs
|
||||
Look for these log entries:
|
||||
```
|
||||
[INFO] Migration status check completed - needsMigration: true
|
||||
[INFO] Starting automatic database migration
|
||||
[INFO] Creating migration backup
|
||||
[SUCCESS] Migration backup created successfully
|
||||
[INFO] Found tables to migrate - tableCount: 8
|
||||
[SUCCESS] Migration integrity verification completed
|
||||
[INFO] Creating encrypted database file
|
||||
[SUCCESS] Database migration completed successfully
|
||||
```
|
||||
|
||||
### Migration Skipped (Safety) Logs
|
||||
```
|
||||
[INFO] Migration status check completed - needsMigration: false
|
||||
[INFO] Both encrypted and unencrypted databases exist. Skipping migration for safety
|
||||
[WARN] Manual intervention may be required
|
||||
```
|
||||
|
||||
### Migration Failure Logs
|
||||
```
|
||||
[ERROR] Database migration failed
|
||||
[ERROR] Backup available at: /app/data/db.sqlite.migration-backup-{timestamp}
|
||||
[ERROR] Manual intervention required to recover data
|
||||
```
|
||||
|
||||
## Manual Recovery Procedures
|
||||
|
||||
### If Migration Fails:
|
||||
1. **Locate backup file**: `db.sqlite.migration-backup-{timestamp}`
|
||||
2. **Restore original**: `cp backup-file db.sqlite`
|
||||
3. **Check logs**: Look for specific error details
|
||||
4. **Fix issue**: Address the root cause (permissions, disk space, etc.)
|
||||
5. **Retry**: Restart container to trigger migration again
|
||||
|
||||
### If Both Databases Exist:
|
||||
1. **Check dates**: Determine which file is newer
|
||||
2. **Backup both**: Make copies before proceeding
|
||||
3. **Remove older**: Delete the outdated database file
|
||||
4. **Restart**: Container will detect single database
|
||||
|
||||
### Emergency Data Recovery:
|
||||
1. **Backup files are SQLite**: Can be opened with any SQLite client
|
||||
2. **Manual export**: Use SQLite tools to export data
|
||||
3. **Re-import**: Use Termix import functionality
|
||||
|
||||
## Performance Expectations
|
||||
|
||||
| Database Size | Expected Migration Time | Memory Usage |
|
||||
|---------------|------------------------|--------------|
|
||||
| < 10MB | < 5 seconds | < 50MB |
|
||||
| 10-50MB | 5-15 seconds | < 100MB |
|
||||
| 50-200MB | 15-45 seconds | < 200MB |
|
||||
| 200MB+ | 45+ seconds | < 500MB |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After migration, verify:
|
||||
- [ ] All SSH hosts are accessible
|
||||
- [ ] SSH credentials work correctly
|
||||
- [ ] File manager recent/pinned items preserved
|
||||
- [ ] User settings maintained
|
||||
- [ ] OIDC configuration intact
|
||||
- [ ] Admin users still have admin privileges
|
||||
- [ ] Backup file exists and is valid SQLite
|
||||
- [ ] Original file renamed (not deleted)
|
||||
- [ ] Encrypted file is properly encrypted
|
||||
- [ ] Migration APIs respond correctly
|
||||
|
||||
## Monitoring Commands
|
||||
|
||||
```bash
|
||||
# Watch migration in real-time
|
||||
docker logs -f container_id | grep -i migration
|
||||
|
||||
# Check file sizes before/after
|
||||
ls -la /host/data/db.sqlite*
|
||||
|
||||
# Verify encrypted file
|
||||
file /host/data/db.sqlite.encrypted
|
||||
|
||||
# Monitor system resources during migration
|
||||
docker stats container_id
|
||||
|
||||
# Test database connectivity after migration
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
http://localhost:8081/hosts/list
|
||||
```
|
||||
|
||||
## Common Issues & Solutions
|
||||
|
||||
### Issue: "Permission denied" during backup creation
|
||||
**Solution**: Check container file permissions and volume mounts
|
||||
|
||||
### Issue: "Insufficient disk space" during migration
|
||||
**Solution**: Free up space, migration requires 2x database size temporarily
|
||||
|
||||
### Issue: "Database locked" error
|
||||
**Solution**: Ensure no other processes are accessing the database file
|
||||
|
||||
### Issue: Migration hangs indefinitely
|
||||
**Solution**: Check for very large BLOB data, increase timeout or migrate manually
|
||||
|
||||
### Issue: Encrypted file fails validation
|
||||
**Solution**: Check DATABASE_KEY environment variable, ensure it's stable
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Backup files contain unencrypted data**: Secure backup file access
|
||||
- **Migration logs may contain sensitive info**: Review log retention policies
|
||||
- **Temporary files during migration**: Ensure secure temp directory
|
||||
- **Original files are preserved**: Plan for secure cleanup of old files
|
||||
- **Admin API access**: Ensure proper authentication and authorization
|
||||
|
||||
## Integration with CI/CD
|
||||
|
||||
For automated testing in CI/CD pipelines:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Migration integration test
|
||||
set -e
|
||||
|
||||
# Start with unencrypted test data
|
||||
docker run -d --name test-migration \
|
||||
-v ./test-data:/app/data \
|
||||
termix:latest
|
||||
|
||||
# Wait for startup
|
||||
sleep 30
|
||||
|
||||
# Check migration status
|
||||
RESPONSE=$(curl -s -H "Authorization: Bearer $TEST_TOKEN" \
|
||||
http://localhost:8081/database/migration/status)
|
||||
|
||||
# Validate migration success
|
||||
echo "$RESPONSE" | jq '.migrationStatus.needsMigration == false'
|
||||
|
||||
# Cleanup
|
||||
docker stop test-migration
|
||||
docker rm test-migration
|
||||
```
|
||||
|
||||
This comprehensive testing approach ensures the migration system handles all edge cases safely and provides administrators with full visibility into the migration process.
|
||||
Reference in New Issue
Block a user