Zum Inhalt

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:

openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

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:

  1. Neuer FIELD_ENCRYPTION_KEY generieren
  2. 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()
  1. .env mit neuem Key ersetzen
  2. 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