Ausgangslage
Unser Watch & Alert Setup läuft seit 18 Monaten auf einem Raspberry Pi 4. Wir nutzen RSS-Feeds für Monitoring, speichern Alerts in einer SQLite-Datenbank und klassifizieren Meldungen mit Ollama lokal. Die Geheimnisse – Telegram-Tokens, API-Schlüssel für Drittanbieter – sind in einer config.yaml gespeichert. Die Rotation erfolgte bisher manuell. Ein einfacher git pull reichte nicht, da die Schlüssel im Code verankert waren.
Bei der letzten Rotation musste das System heruntergefahren werden, um die neue Konfiguration zu laden. Der Telegram-Bot war 45 Sekunden offline. Für 50 Benutzer mit täglichen Updates war das unakzeptabel. Der Ausfall führte zu zwei Meldungen, die nicht gesendet wurden. Das war das letzte Mal, dass wir manuell rotieren.
Unser System hat keine Kubernetes-Cluster oder Microservices. Es ist ein klassisches, auf einem Einzelserver laufendes Python-Skript mit cron-gesteuerter Ausführung. Die Geheimnisse sind kritisch für die Kommunikation, aber wir haben keine Compliance-Vorgaben. Der Aufwand für eine Lösung musste unter 3 Stunden liegen, inklusive Testing.
Anforderungen
Die Lösung musste folgende Kriterien erfüllen:
- Keine Ausfallzeiten während der Rotation
- Keine neuen Abhängigkeiten (kein Vault, kein AWS Secrets Manager)
- Automatisierung via cron (keine manuelle Eingriffe)
- Integration in den bestehenden SQLite-Workflow
- Klare Logging-Unterstützung für Fehlerbehebung
Die Umsetzung sollte maximal 400 Zeilen Python-Code benötigen. Die Rotation musste innerhalb von 90 Tagen automatisch erfolgen, um Verfallsdaten zu berücksichtigen. Wir wollten keine neuen Tools einsetzen – die Lösung musste in unserem bestehenden Projekt verankert sein.
Drei Optionen im Vergleich
Option 1: Umgebungsvariablen mit Service-Neustart
Die meisten DevOps-Workflows nutzen Umgebungsvariablen für Secrets. Wir hatten jedoch die Konfiguration in einer Datei gespeichert. Ein Wechsel hätte einen Neustart des gesamten Systems erfordert. Die Ausfallzeit lag bei 45 Sekunden. Für ein System mit 50 Alerts pro Tag war das unakzeptabel. Der Aufwand für eine Umstellung war zu hoch, da wir das Skript nicht über eine Umgebungsvariable laden konnten.
Option 2: HashiCorp Vault
Vault ist eine etablierte Lösung für Secret Rotation. Wir hätten eine Vault-Instanz aufsetzen müssen. Dafür hätten wir eine zusätzliche VM benötigt und die Konfiguration der API-Endpunkte angepasst. Der Aufwand für die Integration hätte 8 Stunden gekostet, inklusive Testing. Für ein Projekt mit 10 Feeds und 50 Alerts pro Tag war das eindeutig Overkill. Die Dokumentation war komplex, und wir hätten keine Zeit für den Lernprozess gehabt.
Option 3: Dual-Key-System mit SQLite-Updates
Dieser Ansatz nutzte unser bestehendes SQLite-Setup. Wir speicherten den neuen Schlüssel in einer separaten Spalte und ermöglichten einen Übergangszeitraum, in dem beide Schlüssel akzeptiert wurden. Der Service prüfte beide Schlüssel während der Rotation. Die Lösung benötigte keine neuen Abhängigkeiten und passte sich an unseren bestehenden Workflow an. Die Implementierung war aufwändig, aber die Vorteile überwogen deutlich.
Die Lösung / Migration / Umsetzung
Schritt 1: Datenbankschema anpassen
Wir haben eine neue Spalte rotation_time in die secrets-Tabelle hinzugefügt. Der alte Schlüssel bleibt bis zur nächsten Rotation aktiv. So können wir den Übergang überwachen.
def add_rotation_column() -> None:
"""Add rotation_time column to secrets table."""
with sqlite3.connect(DB_PATH) as conn:
try:
conn.execute(
"ALTER TABLE secrets ADD COLUMN rotation_time TEXT"
)
except sqlite3.OperationalError:
pass # Column already exists
Schritt 2: Rotationsskript entwickeln
Das Skript generiert einen neuen Schlüssel, aktualisiert die Datenbank und sendet einen Reload-Befehl an den Hauptprozess. Es prüft vorher, ob der Service läuft.
def rotate_secret(new_secret: str) -> None:
"""Rotate secret and trigger reload."""
with sqlite3.connect(DB_PATH) as conn:
conn.execute(
"UPDATE secrets SET value = ?, rotation_time = ? WHERE key = 'telegram_token'",
(new_secret, datetime.now().isoformat())
)
conn.execute(
"INSERT OR IGNORE INTO secrets (key, value, rotation_time) "
"VALUES ('telegram_token_new', ?, ?)",
(new_secret, datetime.now().isoformat())
)
if is_service_running():
subprocess.run(["kill", "-HUP", str(os.getpid())])
else:
logger.error("Service not running during rotation")
Schritt 3: Übergangsphase im Service
Der Service prüft vor jedem Telegram-Call beide Schlüssel. Nach 30 Tagen wird der alte Schlüssel gelöscht.
def get_telegram_token() -> str:
"""Get active token, prefer new one."""
with sqlite3.connect(DB_PATH) as conn:
cursor = conn.execute(
"SELECT value FROM secrets WHERE key IN ('telegram_token', 'telegram_token_new') "
"ORDER BY rotation_time DESC LIMIT 1"
)
return cursor.fetchone()[0]
Schritt 4: Automatisierung via cron
Der Cronjob führt die Rotation alle 90 Tage aus. Es gibt eine Prüfung vor der Rotation, ob der Service läuft.
# /etc/cron.d/secret-rotation
0 0 * * * /usr/bin/python3 /opt/watch-alert/rotate_secrets.py
Schritt 5: Fehlerbehandlung
Falls der neue Schlüssel nicht funktioniert, wird der alte Schlüssel wieder aktiviert. Wir loggen jeden Schritt in die SQLite-Datenbank.
def test_new_token() -> bool:
"""Test new token before switching."""
try:
send_telegram_message("Test message", token=get_telegram_token())
return True
except Exception as e:
logger.error(f"Token test failed: {e}")
return False
Was wir gelernt haben
Erkenntnis 1: Dual-Key-System erfordert klare Transitionsstrategie
Die Übergangsphase war kritisch. Wir haben 30 Tage als Standard festgelegt. Für kritische Schlüssel (Telegram-Bot) haben wir die Zeit auf 15 Tage verkürzt. Ein zu langer Übergang hätte die Fehleranfälligkeit erhöht. Die Entscheidung für 15 Tage war realistisch für unser kleines Projekt.
Erkenntnis 2: Logging ist entscheidend
Ohne Logging hätten wir Fehler nicht nachvollziehen können. Wir haben jeden Schritt in die SQLite-Datenbank geschrieben. Ein Fehler in der Rotation führte zu einem 30-Sekunden-Stillstand – das Logging half, die Ursache zu finden. Die Log-Entscheidung war eine Investition von 30 Minuten, die uns später 4 Stunden Fehlerbehebung erspart hat.
Erkenntnis 3: Testing in der Staging-Umgebung ist unerlässlich
Wir haben die Rotation mit 10 Dummy-Alerts getestet. Das hat gezeigt, dass der Service nach dem Reload 0,2 Sekunden brauchte – nicht 45 Sekunden wie bei einem Neustart. Die Staging-Tests waren entscheidend, um die Zeit zu messen.
Erkenntnis 4: Automatisierung reduziert menschliche Fehler
Die manuelle Rotation führte zu 3 Fällen, in denen der Schlüssel nicht aktualisiert wurde. Die automatisierte Rotation eliminierte diese Fehler. Für 50 Alerts pro Tag ist das ein großer Vorteil.
Erkenntnis 5: Keine neuen Abhängigkeiten – das ist der Schlüssel
Durch die Nutzung von SQLite und Python-Modulen blieben wir im Projekt verankert. Keine neuen Tools, keine neuen Services. Das war für unser kleines Team entscheidend.
Bezug zum Buch
Kapitel 07 "Sicherheit – Vertiefung" im Buch beschreibt die Bedeutung von geplanten Rotationen. Unser Ansatz passt perfekt zu den Prinzipien des Buches. Wir haben keine externen Tools verwendet, sondern das System selbst umgebaut. Das Kapitel betont, dass "Sicherheit im Code beginnt" – und genau das haben wir umgesetzt. Die Umsetzung zeigt, wie man mit minimalen Mitteln eine sichere Rotation erreicht.
Wann du das nachbauen solltest — und wann nicht
Nachbauen, wenn:
- Du dein eigenes Monitoring-System mit SQLite und Python betreibst.
- Du 10 oder weniger Feeds überwachst.
- Du keine Compliance-Vorgaben hast.
- Du eine Rotation alle 90 Tage automatisieren möchtest.
- Du keine neuen Tools einsetzen möchtest.
Nicht nachbauen, wenn:
- Du ein Enterprise-System mit mehr als 100 Feeds verwendest.
- Du bereits Vault oder AWS Secrets Manager nutzt.
- Du Compliance-Vorgaben wie PCI-DSS hast.
- Du eine komplexere Infrastruktur hast (Kubernetes, Microservices).
Die Lösung ist für Indie-Entwickler und kleine Teams konzipiert. Für Enterprise-Lösungen gibt es bessere Tools. Aber für uns war das die richtige Entscheidung: einfacher, verständlich, ohne Overhead. Wenn du ein ähnliches System hast, probiere es aus. Es lohnt sich.