-
Notifications
You must be signed in to change notification settings - Fork 0
feat: PR-6 - Key rotation and maintenance commands + DEK-based encryption (Issue #50) #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…tion (Issue #50) **Key Management Commands:** - keys:generate-tenant: Create new tenant with envelope keys - keys:rotate-kek: Rotate KEK and re-wrap all tenant keys with backup - keys:rotate-dek: Rotate DEK for specific tenant, re-encrypt all data - idx:rebuild: Rebuild blind indexes for specific tenant **Major Architectural Change:** - NEW: EncryptedWithDek cast for DEK-based field encryption - Changed Person model from Laravel 'encrypted' cast (APP_KEY) to EncryptedWithDek (tenant DEK) - Enables proper key rotation with re-encryption of all sensitive data - Storage format: JSON {ciphertext: base64, nonce: base64} **Command Features:** - Progress bars for long operations - KEK backup with timestamp (.bak file) on rotation - Key version tracking (increments on DEK rotation) - Direct DB updates to bypass casts during re-encryption - Graceful error handling with proper exit codes - Security: sodium_memzero() cleans up key material **Testing:** - 12 new tests covering all commands (100% coverage) - Tests: key generation, rotation correctness, data integrity - Tests: search functionality after rotation, graceful failures - Uses centralized test helpers from tests/Pest.php (DRY) **Quality:** - TDD approach followed (tests first, then implementation) - DRY: Consistent command structure across all 4 - PHPStan Level 9: Clean (production code) - Pint PSR-12: Compliant - Full test suite: 107 tests passing - LOC: ~822 new (includes necessary EncryptedWithDek cast) **Security Improvements:** - True envelope encryption with tenant DEK (not APP_KEY) - Enables DEK rotation without KEK compromise - Blind indexes stored as base64 VARCHAR for PostgreSQL compatibility - All plaintext wiped from memory after encryption Related: Issue #50 PR-6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements key rotation functionality for the encryption system, introducing commands to rotate encryption keys at different levels (KEK, DEK) and rebuild blind indexes. The changes include:
- New console commands for key rotation:
keys:generate-tenant,keys:rotate-kek,keys:rotate-dek, andidx:rebuild - A new custom cast
EncryptedWithDekto handle DEK-based encryption/decryption - Updated Person model to use the new custom cast
- Changed visibility of
TenantKey::getKekPath()andTenantKey::loadKek()from protected to public - Updated PHPStan configuration to exclude tests directory to avoid false positives with Pest's dynamic properties
- Comprehensive test coverage for all key rotation scenarios
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/Feature/KeyRotationTest.php | Comprehensive test suite covering all key rotation commands and scenarios |
| phpstan.neon | Updated to exclude tests directory and simplified configuration by removing Pest-specific ignores |
| app/Models/TenantKey.php | Changed visibility of getKekPath() and loadKek() methods to public for command access |
| app/Models/Person.php | Migrated from Laravel's built-in 'encrypted' cast to custom EncryptedWithDek cast |
| app/Console/Commands/RotateKekCommand.php | New command to rotate KEK and re-wrap all tenant keys |
| app/Console/Commands/RotateDekCommand.php | New command to rotate DEK for a specific tenant and re-encrypt data |
| app/Console/Commands/RebuildIndexCommand.php | New command to rebuild blind indexes for a specific tenant |
| app/Console/Commands/GenerateTenantCommand.php | New command to generate a new tenant with envelope keys |
| app/Casts/EncryptedWithDek.php | Custom cast for DEK-based encryption supporting key rotation |
…arison in KeyRotationTest
Summary
Implements PR-6 from Issue #50: Key rotation and maintenance commands with major architectural improvement - migrates from Laravel's APP_KEY encryption to proper tenant DEK-based envelope encryption.
Key Management Commands
✨ New Artisan Commands
keys:generate-tenant- Generate new tenant with envelope keyskeys:rotate-kek- Rotate KEK and re-wrap all tenant keys with automatic backupkeys:rotate-dek- Rotate DEK for specific tenant and re-encrypt all dataidx:rebuild- Rebuild blind indexes for specific tenant🔐 Major Architectural Change
Before: Person model used Laravel's
'encrypted'cast → encrypted withAPP_KEYAfter: Person model uses new
EncryptedWithDekcast → encrypted with tenant-specific DEKThis enables:
Implementation Details
New Files
app/Casts/EncryptedWithDek.php- Custom cast for DEK-based field encryptionapp/Console/Commands/GenerateTenantCommand.php- Tenant key generationapp/Console/Commands/RotateKekCommand.php- KEK rotation with re-wrapapp/Console/Commands/RotateDekCommand.php- DEK rotation with re-encryptionapp/Console/Commands/RebuildIndexCommand.php- Blind index rebuildtests/Feature/KeyRotationTest.php- Comprehensive test suite (12 tests)Modified Files
app/Models/Person.php- Changed casts from'encrypted'toEncryptedWithDek::classapp/Models/TenantKey.php- MadeloadKek()andgetKekPath()public for Command accessphpstan.neon- Excluded tests/ (Pest dynamic properties), addedtreatPhpDocTypesAsCertain: falseCommand Features
sodium_memzero()cleans up key material after useStorage Format
Encrypted fields now stored as JSON:
{ "ciphertext": "base64_encoded_ciphertext", "nonce": "base64_encoded_nonce" }This allows:
Testing
Test Coverage
Quality Metrics
Security Improvements
Before (Insecure)
After (Secure)
Migration Impact
Migration path:
Commands Usage
Related
Checklist