Feldverschlüsselung¶
Sensitive Spalten in der DB werden mit AES-256-GCM verschlüsselt. Im Klartext liegen sie nur kurz im Worker-Prozess vor, wenn sie für einen Check gebraucht werden.
Welche Felder¶
| Tabelle | Spalte | Wofür |
|---|---|---|
hosts |
snmp_community |
SNMPv1/v2c-Community |
hosts |
ssh_password |
SSH-Login-Passwort (selten — Key bevorzugt) |
hosts |
ssh_private_key |
PEM-Inhalt |
host_services |
config_overrides.password |
beliebiger Passwort-Override |
notification_channels |
webhook.secret |
HMAC-Signatur-Secret |
ai_config |
api_key |
Cloud-LLM-API-Keys |
Implementierung: shared/encryption.py mit encrypt_field() / decrypt_field().
Key¶
FIELD_ENCRYPTION_KEY ist Base64url-codiert, 32 Bytes. Im Setup-Script generiert mit:
Liegt in .env und in secrets/field_encryption_key.
Lebenszyklus¶
sequenceDiagram
participant U as User/UI
participant API
participant DB
participant W as Worker
U->>API: Host anlegen mit snmp_community="public"
API->>API: encrypt_field("public") mit FIELD_ENCRYPTION_KEY
API->>DB: INSERT hosts (snmp_community = b64(nonce||ciphertext||tag))
Note over DB: Klartext nirgends gespeichert
W->>DB: SELECT host für Check
W->>W: decrypt_field(snmp_community)
W->>W: SNMP-Walk mit Klartext
Note over W: Klartext nur in RAM, sofort verworfen
API-Maskierung¶
HostOut (Response-Schema in api/app/schemas) maskiert verschlüsselte Felder als "***". Selbst Super-Admin sieht den Klartext nicht — er kann ihn nur setzen, nicht lesen.
Legacy-Migration¶
Vor v0.x gab es Felder im Klartext. decrypt_field() hat einen Fallback: wenn der Wert nicht das verschlüsselte Format hat (kein Magic-Byte), wird er als unverschlüsselt zurückgegeben. So funktionieren alte Daten weiter, neue werden verschlüsselt.
Bei Set-Operationen wird immer verschlüsselt geschrieben — alter Klartext-Eintrag wird beim ersten Update zu verschlüsseltem Eintrag.
Key-Verlust¶
Schlüsselverlust = Daten unwiederbringlich
Ohne FIELD_ENCRYPTION_KEY lassen sich verschlüsselte Felder nie wieder lesen. Es gibt keinen Recovery-Mechanismus.
Konsequenz: Backup des Schlüssels ist genauso wichtig wie das DB-Backup. Empfehlung in Backup & Restore.
Key-Rotation¶
Aktuell nicht via UI unterstützt. Manueller Pfad:
- Neuer
FIELD_ENCRYPTION_KEYgenerieren - Skript laufen lassen, das alle verschlüsselten Spalten mit altem Key entschlüsselt und mit neuem Key wieder verschlüsselt:
# scripts/rotate_field_key.py — sehr abgekürzte Version
from shared.encryption import decrypt_field, encrypt_field
from sqlalchemy.orm import Session
def rotate(db: Session, old_key: str, new_key: str):
for host in db.query(Host).filter(Host.snmp_community.isnot(None)).all():
plaintext = decrypt_field(host.snmp_community, key=old_key)
host.snmp_community = encrypt_field(plaintext, key=new_key)
db.commit()
.envmit neuem Key ersetzen- Stack neu starten
Während der Rotation kein Schreibzugriff. Sehr aufmerksam mit Backup vorgehen.
Verschlüsselung in Backups¶
PostgreSQL-Dumps enthalten die verschlüsselten Werte (nicht den Klartext, nicht den Schlüssel). Bei Restore in eine Instanz mit anderem FIELD_ENCRYPTION_KEY sind die Werte unleserlich — also Restore immer mit gleichem Key.
Daher: .env-Backup zwingend zusammen mit DB-Backup, aber nicht im selben Container:
- DB-Backup: Volume / S3
.env: Passwort-Manager / verschlüsselter separater Speicher
Anschluss¶
- Backup & Restore — wie Schlüssel und Backups zusammen behandelt werden
- Härtungs-Checkliste