
Einfacher sicherer Verwahrer für Geheimnisse
Keeper ist ein kryptografischer Geheimnisspeicher für Go. Er verschlüsselt beliebige Byte- Nutzlasten im Ruhezustand mittels Argon2id-Schlüsselableitung und XChaCha20-Poly1305 (Standard) authentifizierter Verschlüsselung und speichert sie in einer eingebetteten bbolt-Datenbank.
Er wird in drei unabhängig nutzbaren Komponenten ausgeliefert:
x/keephandler) — binde Keeper-Endpunkte in einem Aufruf an einen
beliebigen net/http-Mux, mit steckbaren Hooks, Guards und Response-
Encodern für Zugriffskontrolle und Audit-Logging.cmd/keeper) — ein Terminal-Interface mit persistenter REPL-Sitzung,
geheimnisvoller (echo-freier) Eingabe und keiner Shell-Verlaufsbelastung.Keeper wurde als grundlegende Geheimnisverwaltungsschicht für den Agbero-Lastenausgleicher entworfen, hat aber keine Abhängigkeit von Agbero und funktioniert in jedem Go-Projekt.
Keeper unterteilt Geheimnisse in Buckets. Jeder Bucket hat eine unveränderliche
BucketSecurityPolicy, die festlegt, wie sein Datenverschlüsselungsschlüssel (DEK) geschützt wird.
Vier Stufen sind verfügbar.
Ein Schema ist ein URI-Präfix, das zusammengehörige Buckets gruppiert (vault://, certs://,
space:// oder ein beliebiger von dir registrierter Name). Eine Sicherheitsstufe ist eine Eigenschaft der
Bucket-Richtlinie, die bei der Erstellung festgelegt wird und danach unveränderlich ist.
Du kannst Sicherheitsstufen innerhalb desselben Schemas frei mischen. Zum Beispiel
könnte vault://system LevelPasswordOnly sein (beim Start automatisch entsperrt), während
vault://admin LevelAdminWrapped ist (erfordert explizite Anmeldeinformationen).
Der Bucket-DEK wird aus dem Masterschlüssel mit HKDF-SHA256 und einem
domänenseparierten Info-String pro Bucket (keeper-bucket-dek-v1:scheme:namespace) abgeleitet.
Alle LevelPasswordOnly-Buckets werden automatisch entsperrt, wenn
UnlockDatabase mit der korrekten Master-Passphrase aufgerufen wird. Es sind keine
pro-Bucket-Anmeldeinformationen zur Laufzeit erforderlich. Diese Stufe eignet sich für Geheimnisse, die der
Prozess beim Start ohne menschliche Interaktion benötigt.
Der Bucket hat einen zufällig generierten 32-Byte-DEK, der für diesen Bucket eindeutig ist. Der DEK
wird niemals im Klartext gespeichert. Für jeden autorisierten Administrator wird ein Schlüsselverschlüsselungsschlüssel
(KEK) aus HKDF(masterKey‖adminCred, dekSalt) abgeleitet und verwendet, um
den DEK mittels XChaCha20-Poly1305 zu verpacken. Der Bucket ist unzugänglich, bis ein Administrator
UnlockBucket mit seinen Anmeldeinformationen aufruft. Die Master-Passphrase allein kann
den Bucket nicht entschlüsseln. Das Widerrufen eines Administrators hat keine Auswirkungen auf die verpackte Kopie
eines anderen Administrators.
Der Bucket-DEK wird zum Zeitpunkt von CreateBucket generiert und sofort von einem
aufruferseitig bereitgestellten HSMProvider verpackt. Der Provider führt die Ver- und Entpackoperationen
durch — Keeper hat nach der Übergabe an den Provider nie Zugriff auf den rohen DEK.
UnlockDatabase ruft automatisch den Provider auf, um das
Envelope für alle registrierten HSM-Buckets zu entpacken und zu initialisieren. Die Rotation des Masterschlüssels
verschlüsselt diese Buckets nicht neu; der DEK wird vom Provider kontrolliert.
Eine integrierte SoftHSM-Implementierung, die von einem memguard-geschützten Verpackungsschlüssel
unterstützt wird, ist in pkg/hsm für Test- und CI-Umgebungen verfügbar. Verwende sie nicht
in der Produktion.
Identisch zu LevelHSM im Schlüsselverwaltungsverhalten, aber der HSMProvider wird
von pkg/remote.Provider implementiert — einem konfigurierbaren HTTPS-Adapter, der
Ver- und Entpacken an jeden entfernten KMS-Dienst über TLS delegiert. Vorgefertigte
Konfigurationen für HashiCorp Vault Transit, AWS KMS und GCP Cloud KMS sind
in pkg/remote enthalten. Für den Produktionseinsatz konfiguriere TLSClientCert und
TLSClientKey, um gegenseitige TLS-Authentifizierung zu aktivieren.
salt ← random 32 bytes, generated once, stored as a versioned SaltStore (unencrypted) masterKey ← Argon2id(passphrase, salt, t=3, m=64 MiB, p=4) → 32 bytes
Ein Verifikations-Hash wird bei der ersten Ableitung gespeichert:```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes
Nachfolgende DeriveMaster-Aufrufe berechnen diesen Hash neu und vergleichen ihn mit crypto/subtle.ConstantTimeCompare. Bei einer Nichtübereinstimmung wird ErrInvalidPassphrase zurückgegeben.
Das KDF-Salz wird absichtlich unverschlüsselt gespeichert. Es muss vor UnlockDatabase lesbar sein, um den Masterschlüssel abzuleiten — eine Verschlüsselung mit einem vom Master abgeleiteten Schlüssel wäre zirkulär. Ein KDF-Salz ist kein Geheimnis; sein Zweck ist Einzigartigkeit, nicht Vertraulichkeit.
Jeder Klartextwert wird mit XChaCha20-Poly1305 unter Verwendung des Bucket-DEK verschlüsselt:``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
Der gespeicherte Datensatz ist eine msgpack-kodierte `Secret`-Struktur, die den Chiffretext, verschlüsselte Metadaten und die Schema-Version enthält. Die Authentifizierung ist implizit: Ein mit dem falschen Schlüssel entschlüsselter Chiffretext führt zu einem AEAD-Authentifizierungsfehler, bevor ein Klartext zurückgegeben wird.
### KEK derivation — LevelAdminWrapped```
salt ← random 32 bytes, generated at bucket creation, stored in policy
ikm ← masterKey ‖ adminCredential
KEK ← HKDF-SHA256(ikm, salt, info="keeper-kek-v1") → 32 bytes
wrappedDEK ← XChaCha20-Poly1305.Seal(nonce, KEK, DEK)
Der KEK wird mittels HKDF und nicht mit einem zweiten Argon2-Durchlauf abgeleitet. Der Hauptschlüssel wurde bereits durch eine KDF mit hohem Kostenaufwand erzeugt; ein zweiter Argon2-Aufruf würde jedem UnlockBucket-Aufruf Hunderte von Millisekunden Latenz hinzufügen, ohne Sicherheitsvorteil. HKDF-SHA256 arbeitet in etwa einer Mikrosekunde.
Verteidigung in der Tiefe: Ein Angreifer, der nur die Datenbank kompromittiert, erhält den umhüllten DEK und das HKDF-Salz, kann aber den KEK ohne den Hauptschlüssel nicht ableiten. Ein Angreifer, der nur den Hauptschlüssel kompromittiert, kann keinen LevelAdminWrapped-DEK entschlüsseln, ohne auch die Admin-Anmeldeinformationen zu kennen.
Geheime Metadaten (Erstellungszeit, Aktualisierungszeit, Zugriffszahl, Version) werden getrennt vom Chiffretext verschlüsselt:``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
Für `LevelAdminWrapped`, `LevelHSM` und `LevelRemote`-Buckets bedeutet dies, dass Metadaten ohne die Bucket-Berechtigung unzugänglich sind, was einen Angreifer mit Lesezugriff auf die Datenbankdatei daran hindert, Zugriffsmuster oder Zeitstempel zu erfahren.