Automated Credential Rotation
Automated Credential Rotation
180-Day Rotation Policy
SMTP credentials are automatically rotated every 180 days to maintain security:
Rotation Schedule:
-
Frequency: Every 180 days
-
Trigger: Automated cron job (daily check at 02:00 UTC)
-
Downtime: Zero (MailU supports immediate password change)
-
Notification: Email to admins 7 days before rotation
Rotation Workflow:
sequenceDiagram
participant Cron
participant RotationService
participant Vault
participant MailU
participant AuditLog
participant Notification
Cron->>RotationService: Check Rotation Due (02:00 UTC)
RotationService->>Vault: Get SMTP Credentials
Vault-->>RotationService: Return Credentials + Metadata
RotationService->>RotationService: Check last_rotated >= 180 days
alt Rotation Due
RotationService->>RotationService: Generate New Password
RotationService->>RotationService: Encrypt New Password
RotationService->>MailU: Update Admin Password
MailU-->>RotationService: Confirmation
RotationService->>Vault: Store New Encrypted Password
Vault-->>RotationService: Confirmation
RotationService->>AuditLog: Log Rotation Event
RotationService->>Notification: Send Rotation Notification
Notification->>Notification: Email Admins
else Not Due
RotationService->>RotationService: Skip Rotation
end
Implementation:
// Automated rotation service (runs daily)
async function checkAndRotateSmtpCredentials(): Promise<void> {
// Get all tenants with SMTP credentials
const tenants = await getTenantsWith SmtpCredentials();
for (const tenant of tenants) {
// Get credentials from Vault
const vaultData = await vaultClient.read(`smtp/${tenant.id}/admin`);
// Check if rotation is due
const lastRotated = new Date(vaultData.last_rotated);
const daysSinceRotation = (Date.now() - lastRotated.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceRotation >= 180) {
// Rotation due - execute rotation
await rotateSmtpCredentials(tenant.id);
} else if (daysSinceRotation >= 173) {
// 7 days before rotation - send notification
await sendRotationNotification(tenant.id, 180 - daysSinceRotation);
}
}
}
// Rotate SMTP credentials for a tenant
async function rotateSmtpCredentials(tenantId: string): Promise<void> {
// Get current credentials
const vaultData = await vaultClient.read(`smtp/${tenantId}/admin`);
const currentPassword = await decryptPassword(vaultData.password, tenantId);
// Generate new password
const newPassword = crypto.randomBytes(32).toString('base64');
// Update MailU password
await updateMailUPassword(
tenant.vps_ip,
vaultData.username,
currentPassword,
newPassword
);
// Encrypt new password
const encryptedPassword = await encryptPassword(newPassword, tenantId);
// Update Vault with new password
await vaultClient.write(`smtp/${tenantId}/admin`, {
...vaultData,
password: encryptedPassword,
last_rotated: new Date().toISOString()
});
// Log rotation event
await auditLog.create({
event: 'smtp_credentials_rotated',
tenant_id: tenantId,
user_id: 'system',
timestamp: new Date().toISOString(),
details: {
rotation_type: 'automated',
previous_rotation: vaultData.last_rotated
}
});
// Send notification to admins
await sendRotationCompletedNotification(tenantId);
}
Manual Rotation
Admins can manually trigger credential rotation at any time:
Use Cases:
-
Security incident (suspected credential compromise)
-
Compliance requirement (immediate rotation)
-
Troubleshooting (reset to known state)
Manual Rotation Workflow:
// API endpoint for manual rotation
async function manualRotateSmtpCredentials(
tenantId: string,
adminUserId: string,
reason: string
): Promise<void> {
// Verify admin role
const admin = await verifyAdminRole(adminUserId);
if (!admin.hasRole('platform-admin')) {
throw new Error('Insufficient permissions');
}
// Execute rotation
await rotateSmtpCredentials(tenantId);
// Log manual rotation with reason
await auditLog.create({
event: 'smtp_credentials_rotated',
tenant_id: tenantId,
user_id: adminUserId,
timestamp: new Date().toISOString(),
details: {
rotation_type: 'manual',
reason: reason
}
});
}
UI Component:
// Manual rotation button in admin dashboard
<Button
variant="danger"
onClick={() => {
if (confirm('Rotate SMTP credentials? This will update the MailU password immediately.')) {
manualRotateSmtpCredentials(tenantId, adminUserId, 'Manual rotation by admin');
}
}}
>
Rotate Credentials Now
</Button>