Vault Restoration from Backup
Step-by-Step Runbook
Prerequisites
- Access to the encrypted S3 backup bucket
- Backup encryption key material
- Vault unseal keys (3 of 5 required)
- Root token or administrator credentials
Step 1: Provision New Vault Server
# Install Vault and configure systemd service
wget https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip
unzip vault_1.15.0_linux_amd64.zip
sudo mv vault /usr/local/bin/
sudo mkdir -p /etc/vault
sudo tee /etc/vault/config.hcl >/dev/null <<'EOF'
storage "postgresql" {
connection_url = "postgres://vault:password@localhost:5432/vault"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault/tls/vault.crt"
tls_key_file = "/etc/vault/tls/vault.key"
}
api_addr = "https://vault.penguinmails.com:8200"
cluster_addr = "https://vault.penguinmails.com:8201"
ui = true
EOF
sudo systemctl enable vault
sudo systemctl start vault
Step 2: Download Latest Backup
BACKUP_DATE=$(date +%Y-%m-%d)
aws s3 cp \
s3://penguinmails-vault-backups/daily/${BACKUP_DATE}/vault-snapshot-*.enc \
/tmp/vault-backup.enc
if [ ! -f /tmp/vault-backup.enc ]; then
echo "ERROR: Backup not found for date ${BACKUP_DATE}"
exit 1
fi
Step 3: Decrypt Backup
openssl enc -d -aes-256-gcm \
-in /tmp/vault-backup.enc \
-out /tmp/vault-backup.snap \
-K ${BACKUP_ENCRYPTION_KEY} \
-iv ${BACKUP_IV}
if [ ! -f /tmp/vault-backup.snap ]; then
echo "ERROR: Backup decryption failed"
exit 1
fi
Step 4: Initialize Vault (fresh server only)
vault operator init -key-shares=5 -key-threshold=3
# Capture unseal keys and root token securely
Step 5: Unseal Vault
vault operator unseal <unseal-key-1>
vault operator unseal <unseal-key-2>
vault operator unseal <unseal-key-3>
vault status # Expect "Sealed: false"
Step 6: Restore Snapshot
vault login <root-token>
vault operator raft snapshot restore /tmp/vault-backup.snap
vault status
Step 7: Verify Secrets
vault kv get vps/test-tenant-id/admin_ssh
vault kv get smtp/test-tenant-id/admin
vault kv get api_keys/test-tenant-id/test-key-id
Step 8: Update DNS and Dependent Services
sudo systemctl restart penguinmails-backend
sudo systemctl restart penguinmails-rotation-service
curl -H "X-Vault-Token: ${VAULT_TOKEN}" \
https://vault.penguinmails.com:8200/v1/sys/health
Step 9: Cleanup
rm /tmp/vault-backup.enc
rm /tmp/vault-backup.snap
echo "Vault restored from backup: ${BACKUP_DATE}" >> /var/log/vault/restoration.log
Automated Restoration Script
// Automated Vault restoration from backup
async function restoreVaultFromBackup(
backupDate?: string
): Promise<void> {
const date = backupDate || new Date().toISOString().split('T')[0];
try {
console.log(`Starting Vault restoration from backup: ${date}`);
// Step 1: Download encrypted backup from S3
const backupKey = `daily/${date}/vault-snapshot-${date}.enc`;
const encryptedBackup = await s3.getObject({
Bucket: 'penguinmails-vault-backups',
Key: backupKey
});
if (!encryptedBackup.Body) {
throw new Error(`Backup not found for date: ${date}`);
}
// Step 2: Decrypt backup
const decryptedBackup = await decryptBackup(
encryptedBackup.Body,
backupEncryptionKey
);
// Step 3: Verify checksum
const checksum = crypto.createHash('sha256').update(decryptedBackup).digest('hex');
if (checksum !== encryptedBackup.Metadata.checksum) {
throw new Error('Backup checksum mismatch');
}
// Step 4: Restore Vault snapshot
await vaultClient.sys.restore(decryptedBackup);
// Step 5: Verify secrets are accessible
const testTenantId = 'test-tenant-id';
const testSecret = await vaultClient.read(`smtp/${testTenantId}/admin`);
if (!testSecret) {
throw new Error('Test secret not found after restoration');
}
// Step 6: Log restoration event
await auditLog.create({
event: 'vault_restored_from_backup',
timestamp: new Date().toISOString(),
details: {
backup_date: date,
backup_key: backupKey,
checksum: checksum
}
});
// Step 7: Notify admins
await sendNotification({
type: 'vault_restoration_success',
message: `Vault restored successfully from backup: ${date}`,
timestamp: new Date().toISOString()
});
console.log(`Vault restoration completed successfully from backup: ${date}`);
} catch (error) {
// Log restoration failure
await auditLog.create({
event: 'vault_restoration_failed',
timestamp: new Date().toISOString(),
severity: 'critical',
details: {
backup_date: date,
error: error.message
}
});
// Alert admins
await sendAlert({
type: 'vault_restoration_failure',
severity: 'critical',
message: `Vault restoration failed: ${error.message}`,
backup_date: date
});
throw error;
}
}
// Decrypt backup
async function decryptBackup(
encryptedData: Buffer,
encryptionKey: Buffer
): Promise<Buffer> {
const iv = encryptedData.slice(0, 16);
const authTag = encryptedData.slice(16, 32);
const encrypted = encryptedData.slice(32);
const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted;
}