Key rotation is a critical security practice that limits the lifetime of encryption keys. The Key Management Service implements automated key rotation to ensure that compromised keys have minimal impact and that encryption remains secure over time.
By default, encryption keys expire after 30 days and are retained for 90 days after expiration before permanent deletion.
The task identifies all non-deprecated keys that have passed their expiration date:
const expirationDate = new Date();const deprecatedCount = await this.prismaService.clientEncryptionKey.updateMany({ where: { deprecated: false, expiresAt: { lt: expirationDate, // Less than current date }, }, data: { deprecated: true, updatedAt: new Date(), },});this.logger.log( `Marked ${deprecatedCount.count} keys as deprecated due to expiration`);
Reference: key-rotation.tasks.ts:33-50
Step 2: Clean Up Old Deprecated Keys
Keys that have been deprecated for longer than the retention period are permanently deleted:
this.keyRetentionDays = this.configService.get( 'ENCRYPTION_KEY_RETENTION_DAYS', 90, // Default: 90 days after deprecation);const deleteThreshold = new Date();deleteThreshold.setDate( deleteThreshold.getDate() - this.keyRetentionDays,);const deletedCount = await this.prismaService.clientEncryptionKey.deleteMany({ where: { deprecated: true, expiresAt: { lt: deleteThreshold, }, },});this.logger.log( `Deleted ${deletedCount.count} deprecated keys older than ${this.keyRetentionDays} days`);
Reference: key-rotation.tasks.ts:52-70
Step 3: Identify Problematic Keys
The service monitors encryption operations and identifies keys with high failure rates:
The monitoring service analyzes decryption failures to identify problematic keys:
async findProblematicKeys( failureThresholdPercent: number = 10,): Promise<string[]> { const problematicKeys: any = await this.prismaService.$queryRaw` WITH key_stats AS ( SELECT key_id, SUM(CASE WHEN status = 'failure' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as failure_rate, COUNT(*) as total_operations FROM "EncryptionMetric" WHERE key_id IS NOT NULL AND operation = 'decrypt' GROUP BY key_id HAVING COUNT(*) >= 5 -- Minimum operations threshold ) SELECT key_id FROM key_stats WHERE failure_rate >= ${failureThresholdPercent} ORDER BY failure_rate DESC, total_operations DESC `; return problematicKeys.map((k) => k.key_id);}