Best Practice: Backup, Recovery & Wiederherstellungstests
Kontext
Backup ohne getestete Recovery ist eine Sicherheitsillusion. Die häufigsten Backup-Fehler in der Praxis sind nicht fehlende Backups, sondern ungetestete Recovery-Verfahren, die im Ernstfall scheitern – weil manuelle Schritte fehlen, Schlüssel nicht verfügbar sind oder die IaC-Umgebung des Ziel-Accounts nicht existiert.
Häufige Probleme ohne strukturierte Backup-Recovery-Praxis:
-
Backups sind konfiguriert, aber Restore-Prozedur wurde nie ausgeführt
-
Backup-Verschlüsselungsschlüssel im selben Account wie die Daten (Ransomware)
-
RTO-Ziel = 1 Stunde, aber letzter Restore-Test ergab 4 Stunden
-
PITR aktiviert, aber Restore-Prozedur nicht dokumentiert
Zugehörige Controls
-
WAF-REL-040 – Backup & Recovery Validation
-
WAF-REL-070 – Disaster Recovery Testing
Zielbild
-
Automatisierte Backups mit RPO-alignierten Retentionsperioden
-
Cross-Account-Speicherung verhindert Single-Account-Kompromittierung
-
PITR für granulare Wiederherstellung auf Transaktionsebene
-
Quartalsweise getestete und dokumentierte Restore-Prozedur
Technische Umsetzung
AWS: Cross-Account RDS Backup
# Backup-Ziel: Separates AWS-Account
resource "aws_db_instance" "main" {
identifier = "payment-db-prod"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 100
backup_retention_period = 14 # 14 Tage PITR
backup_window = "02:00-03:00"
deletion_protection = true
copy_tags_to_snapshot = true
tags = var.mandatory_tags
}
# AWS Backup Plan für Cross-Account Replikation
resource "aws_backup_plan" "main" {
name = "payment-db-backup-plan"
rule {
rule_name = "daily-backup"
target_vault_name = aws_backup_vault.main.name
schedule = "cron(0 2 * * ? *)"
lifecycle {
delete_after = 90 # 90 Tage Retention
}
# Cross-Account Kopie in Backup-Account
copy_action {
destination_vault_arn = var.backup_account_vault_arn
lifecycle {
delete_after = 90
}
}
}
}
# Backup Vault mit WORM-Schutz
resource "aws_backup_vault_lock_configuration" "main" {
backup_vault_name = aws_backup_vault.main.name
min_retention_days = 7
max_retention_days = 90
changeable_for_days = 3 # Compliance Mode nach 3 Tagen
}
S3 Versioning + Object Lock
resource "aws_s3_bucket" "data" {
bucket = "payment-production-data-${var.account_id}"
tags = var.mandatory_tags
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_object_lock_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
default_retention {
mode = "GOVERNANCE"
days = 30 # 30 Tage Löschschutz
}
}
}
# Replikation in Backup-Account
resource "aws_s3_bucket_replication_configuration" "data" {
bucket = aws_s3_bucket.data.id
role = aws_iam_role.replication.arn
rule {
id = "backup-account-replication"
status = "Enabled"
destination {
bucket = var.backup_account_bucket_arn
storage_class = "GLACIER_IR"
}
}
}
Azure: Geo-Redundant Database Backup
resource "azurerm_postgresql_flexible_server" "main" {
name = "payment-db-prod"
resource_group_name = azurerm_resource_group.main.name
location = "westeurope"
version = "15"
sku_name = "GP_Standard_D4s_v3"
backup_retention_days = 35 # Maximaler Azure-Wert
geo_redundant_backup_enabled = true # Cross-Region Backup
high_availability {
mode = "ZoneRedundant"
standby_availability_zone = "2"
}
tags = var.mandatory_tags
}
Restore-Test Automatisierung (Bash/AWS CLI)
#!/bin/bash
# scripts/restore-test.sh
# Quartalsweiser Backup-Restore-Test
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RESTORE_DB_ID="payment-db-restore-test-${TIMESTAMP}"
SNAPSHOT_ID=$(aws rds describe-db-snapshots \
--db-instance-identifier payment-db-prod \
--query 'sort_by(DBSnapshots, &SnapshotCreateTime)[-1].DBSnapshotIdentifier' \
--output text)
echo "Using snapshot: ${SNAPSHOT_ID}"
# Restore in isoliertem Test-Subnet
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier "${RESTORE_DB_ID}" \
--db-snapshot-identifier "${SNAPSHOT_ID}" \
--db-instance-class db.t3.micro \
--db-subnet-group-name restore-test-subnet-group \
--no-publicly-accessible
# Warten bis verfügbar
aws rds wait db-instance-available \
--db-instance-identifier "${RESTORE_DB_ID}"
# Verbindungstest
DB_ENDPOINT=$(aws rds describe-db-instances \
--db-instance-identifier "${RESTORE_DB_ID}" \
--query 'DBInstances[0].Endpoint.Address' \
--output text)
echo "DB Endpoint: ${DB_ENDPOINT}"
# Datenintegritätstest
PSQL_CMD="psql -h ${DB_ENDPOINT} -U admin -d payment_db"
ROW_COUNT=$(${PSQL_CMD} -t -c "SELECT COUNT(*) FROM transactions WHERE created_at > NOW() - INTERVAL '24h'")
echo "Rows in last 24h: ${ROW_COUNT}"
# RTO messen
END_TIME=$(date +%s)
echo "Restore completed. Elapsed: $((END_TIME - START_TIME))s"
# Cleanup
aws rds delete-db-instance \
--db-instance-identifier "${RESTORE_DB_ID}" \
--skip-final-snapshot
Typische Fehlmuster
-
Backup im gleichen Account: Ransomware verschlüsselt Backups gemeinsam mit Produktionsdaten
-
Retentionsperiode = 1 Tag: Datenfehler, die erst nach 48h entdeckt werden, können nicht behoben werden
-
Restore-Test in derselben Umgebung wie Produktion: Test benutzt Produktionskonfiguration; im Ernstfall fehlen Ressourcen
-
Manuelle Restore-Anleitung veraltet: Service-URLs, Secrets und IAM-Rollen haben sich geändert
Metriken
-
Backup Success Rate: % der geplanten Backup-Jobs, die erfolgreich waren (Ziel: 100%)
-
Restore Test RTO: Tatsächliche Zeit bis zur Wiederherstellung beim letzten Restore-Test
-
Data Integrity Score: % der validierten Datenpunkte nach Restore (Ziel: 100%)
-
Backup Age: Alter des neuesten verfügbaren Backups (Ziel: < RPO)
Reifegrad
Level 1 – Keine Backups oder Ad-hoc Snapshots
Level 2 – Automatisierte Backups, nie getestet
Level 3 – PITR, Cross-Account, Restore quartalsweise getestet und dokumentiert
Level 4 – Automatisierter monatlicher Restore-Test in Pipeline
Level 5 – WORM-Backups, CDP, kontinuierliche Backup-Integritätsvalidierung