Master-Key-Rotation
Für At-rest-Verschlüsselung
(Object-Storage-Verschlüsselung,
Durable-DD-Verschlüsselung) leben die Schlüssel des Frameworks in
einem MasterKeyRing — einem versionierten Satz von Schlüsseln:
ein active-Schlüssel plus beliebig viele retired-Schlüssel,
jeweils mit einer numerischen Version (ein einzelnes Byte, 0-255)
getaggt. Rotation ist online: Neue Writes laufen unter dem
active-Schlüssel; alte Reads funktionieren weiter unter der Version,
mit der sie verschlüsselt wurden; ein Hintergrund-Sweep verschlüsselt
ältere Daten irgendwann neu.
import type { MasterKeyRing } from 'actor-ts';
// Jeder Schlüssel ist 32 rohe Bytes (AES-256) — dekodiert aus einer// base64-Env-Var, einem gemounteten Secret oder einem KMS-Unwrap// (siehe "Speicherung der Master-Keys" unten).const keyRing: MasterKeyRing = { active: { version: 2, key: Buffer.from(process.env.MASTER_KEY_V2!, 'base64') }, // neu — aktuell retired: [{ version: 1, key: Buffer.from(process.env.MASTER_KEY_V1!, 'base64') }],};MasterKeyRing ist ein Typ, keine Klasse — du baust das obige
einfache Objekt; es gibt nichts zu new. active ist der Schlüssel,
der für neue Writes genutzt wird. Reads dispatchen anhand des
Version-Bytes im Manifest jedes Blobs und matchen es gegen active
oder einen der retired-Einträge.
Weil dieses Byte der einzige Anhaltspunkt des Lesers ist, braucht jeder Eintrag seine eigene Versionsnummer. Ein Ring mit derselben Version auf zwei Einträgen wird abgelehnt — bei der Registrierung, am Store und beim Start eines Sweeps — statt über die Lookup-Reihenfolge aufgelöst zu werden. Siehe jede Version kommt genau einmal vor.
Das Ein-Byte-Feld begrenzt, wie viele Versionen gleichzeitig
leben dürfen, nicht wie oft du rotieren darfst: sobald ein Sweep
den ganzen Bestand auf die active-Version gezogen hat, fallen die
retired-Einträge weg und ihre Nummern werden wieder nutzbar. Ab
Version 240 warnt die Registrierung, damit dafür noch Zeit bleibt.
Wann rotieren
Abschnitt betitelt „Wann rotieren“Drei Auslöser:
- Geplante Rotation — eine Security-Policy (alle 90 Tage, jährlich).
- Vermutete Kompromittierung — geleaktes Schlüsselmaterial; sofort rotieren.
- Compliance — regulatorische Anforderungen schreiben periodische Rotation vor.
Auch ohne spezifischen Auslöser ist periodische Rotation gute Praxis — begrenzt den Blast Radius eines unentdeckten Leaks.
Der Rotations-Ablauf
Abschnitt betitelt „Der Rotations-Ablauf“ 1. Frischen Schlüssel mit der nächsten Versionsnummer erzeugen. Zu keyRing.retired hinzufügen; NOCH NICHT zu active promoten. 2. Den aktualisierten Ring auf allen Nodes ausrollen. Verifizieren, dass Reads funktionieren — alte Daten entschlüsseln weiter, Writes nutzen weiter den aktuellen active-Schlüssel. 3. Den frischen Schlüssel zu active promoten (den bisherigen active nach retired verschieben). Ausrollen. Neue Writes nutzen den neuen Schlüssel; alte Daten weiter lesbar. 4. Den Re-Encryption-Sweep laufen lassen. Alte Daten werden gelesen, entschlüsselt, unter dem active-Schlüssel neu verschlüsselt. 5. Sobald der Sweep durch ist, den alten Schlüssel aus retired droppen. Ein Rollback-Fenster von ~7 Tagen halten, in dem der alte Schlüssel noch verfügbar ist; danach droppen.Das Framework unterstützt jeden dieser Schritte ohne Downtime.
Schritt 1 — den neuen Schlüssel hinzufügen
Abschnitt betitelt „Schritt 1 — den neuen Schlüssel hinzufügen“const keyRing: MasterKeyRing = { active: { version: 1, key: Buffer.from(process.env.MASTER_KEY_V1!, 'base64') }, // weiter v1 — active unverändert retired: [{ version: 2, key: Buffer.from(process.env.MASTER_KEY_V2!, 'base64') }], // v2 zum Ring hinzugefügt, noch nicht active};Diese Config auf jeden Node ausrollen. Reads von v1-verschlüsselten Daten funktionieren weiter; Writes nutzen weiter v1 — aber jeder Node hält jetzt v2 und kann darunter entschlüsseln.
Dieser Schritt ist sicher und umkehrbar — wenn v2 doch nicht
gebraucht wird, einfach aus retired entfernen.
Schritt 2 — den neuen Schlüssel promoten
Abschnitt betitelt „Schritt 2 — den neuen Schlüssel promoten“const keyRing: MasterKeyRing = { active: { version: 2, key: Buffer.from(process.env.MASTER_KEY_V2!, 'base64') }, // ← jetzt v2 retired: [{ version: 1, key: Buffer.from(process.env.MASTER_KEY_V1!, 'base64') }],};Ausrollen. Neue Writes laufen unter v2. Reads konsultieren den Ring und finden den richtigen Schlüssel (v1 oder v2) anhand des Version-Bytes im Manifest des gespeicherten Blobs.
Nach diesem Schritt sammeln sich neue Daten unter v2 an, sobald der Workload schreibt. Alte Daten bleiben unter v1, bis sie neu verschlüsselt werden.
Schritt 3 — Re-Encryption-Sweep
Abschnitt betitelt „Schritt 3 — Re-Encryption-Sweep“import { reEncryptObjectStorage } from 'actor-ts';
// `backend` ist das ObjectStorageBackend, durch das dein Store// schreibt (ein FilesystemObjectStorageBackend, S3ObjectStorageBackend, …).const result = await reEncryptObjectStorage(backend, { keyPrefix: 'snapshots/', // welche Objekte gesweept werden keyring: keyRing, // active = v2, retired = [v1] info: 'acme/prod/snapshot/v1', // der HKDF-Kontext des Stores});
console.log(`re-encrypted ${result.rewrote} of ${result.scanned} objects`);info ist Pflicht und muss exakt der Wert sein, den der
schreibende Store verwendet. Es ist der HKDF-Kontext, also die
Hälfte dessen, woraus der Subkey abgeleitet wird — ein falscher
Wert lässt jede Entschlüsselung fehlschlagen, statt still
falsche Ausgaben zu erzeugen.
Der Sweep listet jedes Objekt unter keyPrefix und entschlüsselt +
verschlüsselt jedes, das noch nicht auf der active-Version des Rings
ist, unter active neu. Objekte, die bereits aktuell sind, werden
ohne Write übersprungen.
Nützliche Optionen:
skip— ein(key) => boolean-Prädikat; matchende Keys bleiben unangetastet (Nicht-Body-Objekte ausschließen oder eine partielle Rotation eingrenzen).onProgress— Per-Objekt-Callback fürs Logging oder ein Operator-Dashboard bei langen Sweeps.progress— einReEncryptProgressStore(z. B.InMemoryReEncryptProgressStore) für Crash-Resume sehr großer Sweeps.verifyKeyringCompleteness— standardmäßig an: sampelt einige Blobs und verweigert den Start, wenn eines eine Version referenziert, die im Ring fehlt.newInfo— rotiert den HKDF-Kontext statt (oder zusätzlich zu) dem Schlüssel; siehe HKDF-Kontext rotieren.
Der Sweep ist idempotent + wiederaufnehmbar — ein Objekt, das
bereits auf der active-Version ist, wird ohne Write übersprungen,
sodass ein erneuter Lauf nach einer Unterbrechung sicher ist.
Standardmäßig re-listet und re-checkt ein wiederaufgenommener Lauf
jeden Key; übergib einen progress-Store, um direkt an den bereits
erledigten Objekten vorbeizuspringen.
Schritt 4 — den alten Schlüssel ausmustern
Abschnitt betitelt „Schritt 4 — den alten Schlüssel ausmustern“Nachdem der Sweep durch ist (jedes Item unter v2 verschlüsselt):
const keyRing: MasterKeyRing = { active: { version: 2, key: Buffer.from(process.env.MASTER_KEY_V2!, 'base64') }, // retired gedroppt — v1 ist weg};v1 komplett droppen. Daten, die weiter unter v1 verschlüsselt sind (z. B. Backups, die nicht neu verschlüsselt wurden), sind jetzt nicht mehr lesbar.
Warte ein Rollback-Fenster vor dem Droppen. ~7 Tage lassen dich davon erholen, falls “ach nein, der Sweep hat doch nicht alle Backups erfasst”. Nach bestätigter Migration v1 droppen.
Speicherung der Master-Keys
Abschnitt betitelt „Speicherung der Master-Keys“Der keyRing liefert kein Key-Storage-Backend mit. Übliche Muster:
| Quelle | Muster |
|---|---|
| Env-Vars | process.env.MASTER_KEY_V2 — einfachste Variante, fein für Tests. |
| K8s-Secrets | Als Files gemountet; beim Start gelesen. |
| HashiCorp Vault | Dynamisch beim Start ziehen; periodisch erneuern. |
| AWS KMS / GCP KMS / Azure Key Vault | Cloud-KMS-APIs. Decrypt-on-Load via die KMS-Encryption-Keys. |
Für Produktion ist KMS die richtige Antwort — Schlüssel verlassen die sichere Grenze nie im Klartext.
Multi-Cluster-Aspekte
Abschnitt betitelt „Multi-Cluster-Aspekte“Wenn mehrere Cluster denselben verschlüsselten Store teilen (z. B. eine DR-Replica, die die Backups der Primary liest), brauchen alle Cluster denselben keyRing. Gemeinsam rotieren; nicht zulassen, dass ein Cluster bei Schlüssel-Generationen zurückfällt.
Failure-Modes
Abschnitt betitelt „Failure-Modes“Wohin als nächstes
Abschnitt betitelt „Wohin als nächstes“- Object-Storage-Verschlüsselung — was die Master-Keys nutzt.
- Object-Storage-Key-Rotation — die Storage-seitigen Mechaniken.
- Cluster-Sicherheit — das In-Transit-Pendant zur At-rest-Verschlüsselung.
- Operations-Überblick — vollständige Security-Checkliste.
