Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
Tools/GitHubGitHub/agberohq/keeper
Authentication & AuthorizationEncryption/Decryption ToolsCryptographySecret Detection
GitHubagberohq/keeper

keeper

Simple Secure Keeper for Secrets

Repository anzeigen
1204vor 4 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

keeper

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:

  • Eine Go-Bibliothek — bette einen gehärteten Geheimnisspeicher direkt in deinen Prozess ein, mit vier Sicherheitsstufen, pro-Bucket-DEK-Isolation und einer manipulationssicheren Auditkette.
  • Ein HTTP-Handler (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.
  • Ein CLI (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.


Inhaltsverzeichnis

  • Sicherheitsmodell
  • Kryptografisches Design
  • Schlüsselhierarchie
  • Speicherschema
  • Auditkette
  • Jack-Integration
  • x/keepcmd — wiederverwendbare CLI-Operationen
  • x/keephandler — HTTP-Handler
  • API-Referenz
  • Fehlerkatalog
  • Sicherheitsentscheidungen
  • Abhängigkeiten

Sicherheitsmodell

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.

Schemata vs. Sicherheitsstufen

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).

LevelPasswordOnly

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.

LevelAdminWrapped

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.

LevelHSM

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.

LevelRemote

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.


Kryptografisches Design

Ableitung des Masterschlüssels```

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

root@kitploit:~
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.

Geheimverschlüsselung

Jeder Klartextwert wird mit XChaCha20-Poly1305 unter Verwendung des Bucket-DEK verschlüsselt:``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)

root@kitploit:~
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.

Metadatenverschlüsselung — Geheimnisse

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))

root@kitploit:~
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.

**Hinweis zu Timing-Seitenkanälen:** XChaCha20-Poly1305 verarbeitet den vollständigen Chiffretext, bevor ein Authentifizierungsfehler zurückgegeben wird. Der Fallback-Entschlüsselungspfad (neuer abgeleiteter DEK → alter Master-Key-als-DEK) benötigt unabhängig davon, welcher Schlüssel erfolgreich ist, die gleiche Wanduhrzeit. Es gibt keinen Timing-Seitenkanal, der den Migrationsstatus eines Datensatzes preisgibt.

### Metadatenverschlüsselung – Richtlinien, WAL und Audit

Alle strukturellen Metadaten werden ebenfalls ruhend verschlüsselt. Zwei Schlüssel werden zum Zeitpunkt von `UnlockDatabase` aus dem Masterschlüssel abgeleitet:```
policyEncKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-enc-v1") → 32 bytes
auditEncKey  ← HKDF-SHA256(masterKey, nil, info="keeper-audit-enc-v1")  → 32 bytes

policyEncKey verschlüsselt: BucketSecurityPolicy-Werte und das Rotations-WAL.

auditEncKey verschlüsselt: die Felder Scheme, Namespace und Details jedes Audit-Ereignisses.

Beide Schlüssel werden bei Lock() aus dem Speicher gelöscht. Die für die Metadatenverschlüsselung verwendete Chiffre ist die gleiche konfigurierbare crypt.Cipher-Schnittstelle, die auch für Geheimnisse verwendet wird — die Wahl des Benutzers (AES-256-GCM für FIPS, standardmäßig XChaCha20-Poly1305) wird automatisch übernommen.

Drahtformat für alle verschlüsselten Metadaten-Blobs:``` nonce (cipher.NonceSize() bytes) || AEAD-ciphertext

root@kitploit:~
### Hashing von Policy-Bucket-Schlüsseln

Auf der Festplatte gespeicherte Policy-Schlüssel sind undurchsichtige Hashes anstelle von Klartext-`scheme:namespace`-Strings, was die Offline-Enumeration von Bucket-Namen verhindert:```
base ← hex(SHA-256("scheme:namespace"))[:32]   // 32 hex chars = 128-bit key space
_policies/<base>          → encrypted BucketSecurityPolicy
_policies/<base>__hash__  → SHA-256(encrypted policy bytes)
_policies/<base>__hmac__  → HMAC-SHA256(policyKey, encrypted policy bytes)

Der In-Memory-schemeRegistry verwendet weiterhin "scheme:namespace" als Schlüssel — nur die On-Disk-Repräsentation ändert sich.

Policy-Authentifizierung

Jeder Policy-Datensatz trägt zwei Integritäts-Tags, die atomar in einer einzigen bbolt-Transaktion geschrieben werden:``` hash ← SHA-256(encryptedPolicyBytes) — unauthenticated, pre-unlock integrity policyKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-hmac-v1") → 32 bytes hmac ← HMAC-SHA256(policyKey, encryptedPolicyBytes) — authenticated, post-unlock integrity

root@kitploit:~
Vor `UnlockDatabase` ist nur der SHA-256-Hash verfügbar. Nach dem Entsperren,
`loadPolicy` überprüft den HMAC-Tag. `UnlockDatabase` ruft `upgradePolicyHMACs` auf,
um HMAC-Tags für Richtlinien nachträglich zu ergänzen, die vor der Einführung dieser Funktion erstellt wurden.

### Prüfung der HMAC-Signierung```
auditKey ← HKDF-SHA256(masterKey, nil, info="keeper-audit-hmac-v1") → 32 bytes
HMAC ← HMAC-SHA256(auditKey, event fields including Seq)

Der Signierschlüssel wird bei UnlockDatabase aktiviert und bei Lock gelöscht. Wenn der Hauptschlüssel rotiert wird, fügt Rotate ein Schlüsselrotations-Checkpoint-Ereignis an jede aktive Audit-Kette an, signiert mit dem alten Audit-Schlüssel als letztes Ereignis der alten Epoche. Die Historie wird nie umgeschrieben; der Checkpoint ist die Vertrauensbrücke zwischen den Epochen.


Schlüsselhierarchie```

passphrase │ └─ Argon2id(salt) ──→ masterKey (32 bytes, memguard Enclave) │ ├─ HKDF("keeper-audit-hmac-v1") ──→ auditKey (HMAC signing) ├─ HKDF("keeper-audit-enc-v1") ──→ auditEncKey (audit field encryption) ├─ HKDF("keeper-policy-hmac-v1") ──→ policyKey (policy HMAC) ├─ HKDF("keeper-policy-enc-v1") ──→ policyEncKey (policy/WAL encryption) │ ├─ [LevelPasswordOnly] │ └─ HKDF("keeper-bucket-dek-v1:scheme:ns") ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ ├─ [LevelAdminWrapped] │ ├─ random 32 bytes ──→ DEK │ │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ │ │ └─ HKDF("keeper-kek-v1", masterKey‖adminCred, dekSalt) │ └─ KEK │ └─ XChaCha20-Poly1305(KEK, DEK) ──→ wrappedDEK │ └─ [LevelHSM / LevelRemote] ├─ random 32 bytes ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ └─ HSMProvider.WrapDEK(DEK) ──→ wrappedDEK (stored; provider controls the wrapping key)

root@kitploit:~
All intermediate keys are zeroed immediately after use. The master key is
never written to disk in any form.

---

## Storage schema

The underlying database is bbolt. All buckets and their contents:

| bbolt bucket | Key | Value |
|---|---|---|
| `__meta__` | `salt` | msgpack — SaltStore (unencrypted; circular dependency if encrypted) |
| `__meta__` | `verify` | raw bytes — Argon2id verification hash |
| `__meta__` | `rotation_wal` | `nonce‖AEAD(msgpack(RotationWAL))` |
| `__meta__` | `bucket_dek_done` | `"1"` — DEK migration completion marker |
| `__policies__` | `hex(SHA-256(scheme:ns))[:32]` | `nonce‖AEAD(msgpack(BucketSecurityPolicy))` |
| `__policies__` | `<base>__hash__` | hex SHA-256 of encrypted policy bytes |
| `__policies__` | `<base>__hmac__` | hex HMAC-SHA256(policyKey, encrypted policy bytes) |
| `__audit__/scheme/namespace` | event UUID | JSON — audit Event |
| `__audit__/scheme/namespace` | `__chain_index__` | JSON — chainIndex |
| `scheme/namespace` | key string | msgpack — Secret struct |

### Secret struct (msgpack)```go
type Secret struct {
    Ciphertext    []byte `msgpack:"ct"`
    EncryptedMeta []byte `msgpack:"em,omitempty"`
    SchemaVersion int    `msgpack:"sv"`  // always 1
}

Felder des Audit-Ereignisses

Die Event-Struktur verwendet separate Klartext-Routing-Felder (Scheme, Namespace) neben verschlüsselten Nutzdatenfeldern (EncScheme, EncNamespace, EncDetails). Prüfsummen werden über die Klartext-Routing-Felder und die verschlüsselten EncDetails-Bytes berechnet, sodass die Kettenintegrität auf drei Ebenen ohne Schlüssel überprüft werden kann:

StufeHatKann überprüfen
ÖffentlichNichtsSHA-256-Prüfsummenkette (erkennt Manipulation und Einfügung)
Audit-SchlüsselinhaberauditEncKey

Beispiel: Ein Compliance-Prüfer erhält nur auditEncKey. Er kann die vollständige HMAC-Kette über Schlüsselrotationen hinweg überprüfen und alle Ereignisdetails lesen, kann jedoch keine geheimen Werte entschlüsseln. Ein öffentlicher Beobachter, der nur die Datenbankdatei besitzt, kann dennoch erkennen, ob ein Ereignis nachträglich geändert oder eingefügt wurde.

Versionierter Salzspeicher

Das KDF-Salz wird als msgpack-codierte SaltStore unter dem Metadatenschlüssel salt gespeichert. Jede Salzrotation fügt einen neuen SaltEntry hinzu und erhöht CurrentVersion. Alte Einträge werden als Prüfpfad aufbewahrt. Der SaltStore wird unverschlüsselt gespeichert — siehe Sicherheitsentscheidungen.

Absturzsicheres Rotations-WAL

Rotate schreibt ein WAL, bevor es einen Datensatz berührt. Das WAL trägt WrappedOldKey: den vor der Rotation verschlüsselten Masterschlüssel mit dem neuen Masterschlüssel. Nach einem Absturz ist die alte Passphrase verloren; WrappedOldKey ist der einzige korrekte Weg, den alten Schlüssel über die Grenze zu tragen. Bei UnlockDatabase, wenn ein WAL vorhanden ist, entschlüsselt der neue Masterschlüssel WrappedOldKey und die Rotation wird ab dem WAL-Cursor fortgesetzt. Das WAL selbst ist mit policyEncKey verschlüsselt.


Audit-Kette

Jeder bedeutende Vorgang hängt ein manipulationssicheres Ereignis an die Audit-Kette des Buckets an. Die Kettenintegrität basiert auf zwei Mechanismen.

Prüfsumme. SHA-256 über prevChecksum, ID, BucketID, Scheme, Namespace, EncDetails, EventType und Timestamp. Die Verwendung von Scheme/Namespace als Klartext (immer zusammen mit den verschlüsselten Formen erhalten) stellt sicher, dass die Prüfsumme über Ladepfade hinweg stabil ist. EncDetails bietet Integrität über die verschlüsselten Nutzdaten.

HMAC. HMAC-SHA256 über alle Felder einschließlich Seq. Ein Angreifer, der in die Datenbank schreiben kann, aber den Audit-Schlüssel nicht kennt, kann keinen gültigen HMAC erzeugen. VerifyIntegrity überprüft beide Schichten für jedes Ereignis.

Epochengrenze der Schlüsselrotation. Bei Rotate wird ein Checkpoint-Ereignis an jede aktive Kette angehängt, das Fingerabdrücke sowohl des ausgehenden als auch des eingehenden Audit-Schlüssels trägt. Der Checkpoint wird mit dem ausgehenden Schlüssel signiert. Prüfer, die einen beliebigen Epochenschlüssel besitzen, können nachfolgende Epochenschlüssel aus dem wrapped_new_key-Feld wiederherstellen und die HMAC-Kontinuität über die gesamte Kette hinweg überprüfen.

Automatische Bereinigung. Wenn AuditPruneInterval in Config gesetzt ist, läuft ein jack.Scheduler periodisch und ruft PruneEvents für jeden registrierten Bucket auf. LevelHSM- und LevelRemote-Bucket werden unabhängig von dieser Einstellung nie bereinigt.


Jack-Integration

Jack ist eine optionale Prozessüberwachungsbibliothek. Wenn ein JackConfig über WithJack bereitgestellt wird, aktiviert Keeper automatisch Hintergrundkomponenten:

  • Auto-Lock Looper: Überprüft regelmäßig den Zeitstempel der letzten Aktivität und verwirft LevelAdminWrapped-Bucket-DEKs nach AutoLockInterval. LevelPasswordOnly-Buckets bleiben entsperrt, sodass Hintergrundjobs ununterbrochen fortgesetzt werden können. Das Single-Write-Lock-Muster innerhalb der Looper-Aufgabe beseitigt die RUnlock→Lock-Race-Condition, die in früheren Entwürfen vorhanden war.
  • Per-Bucket-DEK-Reaper: TTL-basierte Ablaufsteuerung für LevelAdminWrapped-DEKs.
  • Health-Monitoring-Patienten: bbolt-Lese-Latenzprüfung und Ver-/Entschlüsselungs-Rundlaufverifizierung, beide registriert bei jack.Doctor.
  • Audit-Bereinigungsplaner: Periodische PruneEvents auf allen Nicht-HSM-Buckets.
  • Async-Event-Pool: Audit-Ereignisse werden übermittelt, ohne den Hauptvorgang zu blockieren.

Wenn JackConfig nicht bereitgestellt wird, läuft Keeper ohne diese Hintergrundaufgaben. Keeper ruft nie pool.Shutdown auf — der Pool-Lebenszyklus gehört dem Aufrufer.


x/keepcmd

x/keepcmd bietet wiederverwendbare Keeper-Operationen, die von jedem CLI-Framework entkoppelt sind. Betten Sie es in Ihre eigene Anwendung ein, um typisierte, testbare Geheimnisverwaltung zu erhalten, ohne das CLI-Binärprogramm mitziehen zu müssen.```go import "github.com/agberohq/keeper/x/keepcmd"

cmds := &keepcmd.Commands{ Store: func() (*keeper.Keeper, error) { return security.KeeperOpen(cfg) // your own config }, Out: keepcmd.PlainOutput{}, NoClose: false, // true in REPL / session contexts }

cmds.List() // all keys: scheme://namespace/key cmds.List("vault") // all keys in scheme vault cmds.List("vault", "system") // all keys in vault://system cmds.Get("vault://system/jwt_secret") cmds.Set("vault://system/jwt_secret", "newsecret", keepcmd.SetOptions{}) cmds.Rotate(newPassphraseBytes) // caller resolved the passphrase — no prompter dependency cmds.RotateSalt(currentPassBytes) // same

root@kitploit:~
`keepcmd` ruft niemals `prompter` auf oder liest von stdin. Die Passphrase-Auflösung liegt vollständig in der Verantwortung des Aufrufers – dadurch bleibt das Paket in headless server contexts sicher.

`NoClose: true` verhindert, dass `Commands` nach jedem Vorgang `store.Close()` aufruft. Verwenden Sie dies in REPL-/Sitzungskontexten, in denen ein gemeinsamer Store über viele Aufrufe hinweg verwendet wird.

---

## x/keephandler

`x/keephandler` bindet Keeper-HTTP-Endpunkte an jeden `net/http`-Mux. Keine externe Router-Abhängigkeit – es verwendet Go 1.22+ Method- und Pattern-Routing mit der Stdlib `http.ServeMux`.```go
import "github.com/agberohq/keeper/x/keephandler"

keephandler.Mount(mux, store,
    keephandler.WithPrefix("/api/keeper"),
    keephandler.WithGuard(func(w http.ResponseWriter, r *http.Request, route string) bool {
        if !acl.Allow(r.Header.Get("X-Principal"), route) {
            http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
            return false
        }
        return true
    }),
    keephandler.WithHooks(
        keephandler.Hook{
            Route:       keephandler.RouteGet,
            CaptureBody: false,
            After: func(r *http.Request, status int, _ []byte) {
                audit.Log(r.Context(), route, status)
            },
        },
    ),
    keephandler.WithEncoder(func(w http.ResponseWriter, route string, status int, data any) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(map[string]any{
            "ok":    status < 400,
            "route": route,
            "data":  data,
        })
    }),
    keephandler.WithRoutes(func(m *http.ServeMux) {
        m.HandleFunc("POST /api/keeper/totp/{user}", myTOTPHandler)
    }),
)

Endpoints

Hook-Vertrag

BeforeFunc gibt (allow bool, err error) zurück.

  • (true, nil) — die Anfrage fortsetzen lassen.
  • (false, nil) — abbrechen; der Hook hat bereits eine vollständige Antwort geschrieben.
  • (false, err) — abbrechen; das Framework schreibt einen 500 mit err.Error(). Der Hook darf nichts an w geschrieben haben.

Hook.CaptureBody bool steuert, ob AfterFunc den Antworttext erhält. false (Standard) kostet einen leichten statusWriter-Wrapper; true puffert den vollständigen Text in einen bytes.Buffer für den AfterFunc — eine Zuweisung pro Anfrage.

Hooks werden in der Reihenfolge der Registrierung ausgeführt. Mehrere WithHooks-Aufrufe sind additiv. Nur der zuerst für einen bestimmten Routennamen registrierte Hook wird verwendet – spätere Registrierungen für dieselbe Route werden ignoriert.


API-Referenz

Konstruktion und Entsperren```go

store, err := keeper.New(keeper.Config{ DBPath: "/var/lib/agbero/keeper.db", AutoLockInterval: 30 * time.Minute, EnableAudit: true, AuditPruneInterval: 24 * time.Hour, AuditPruneKeepLastN: 10_000, AuditPruneOlderThan: 90 * 24 * time.Hour, DBLatencyThreshold: 200 * time.Millisecond, Logger: logger, }, keeper.WithJack(keeper.JackConfig{ Pool: jackPool, Shutdown: jackShutdown, })) defer store.Close()

// Shorthand (wraps DeriveMaster + UnlockDatabase): if err := store.Unlock([]byte(os.Getenv("KEEPER_PASSPHRASE"))); err != nil { log.Fatal(err) // ErrInvalidPassphrase on wrong passphrase }

root@kitploit:~
`UnlockDatabase` führt die folgenden Schritte in dieser Reihenfolge aus:

1. Leitet den Audit-HMAC-Signaturschlüssel ab und aktiviert ihn
2. Leitet den Policy-HMAC-Schlüssel ab und aktiviert ihn
3. Leitet `policyEncKey` und `auditEncKey` ab und aktiviert sie
4. Löscht und lädt `schemeRegistry` neu (entschlüsselt alle Policy-Blobs)
5. Setzt alle unterbrochenen Rotations-WALs fort
6. Aktualisiert Policy-HMAC-Tags
7. Initialisiert alle `LevelPasswordOnly`-Bucket-DEKs im Envelope
8. Startet Hintergrundaufgaben (Migrations-Looper, Auto-Lock, Health-Patienten)

### LevelPasswordOnly-Bucket – vollständiger Lebenszyklus```go
err := store.CreateBucket("vault", "system", keeper.LevelPasswordOnly, "init")

store.Set("vault://system/jwt_secret", []byte("supersecret"))
val, err := store.Get("vault://system/jwt_secret")

// Namespaced convenience wrappers
store.SetNamespaced("admin", "jwt_secret", secretBytes)
val, err = store.GetNamespaced("admin", "jwt_secret")

LevelAdminWrapped bucket — vollständiger Lebenszyklus```go

err := store.CreateBucket("finance", "payroll", keeper.LevelAdminWrapped, "ops-team") err = store.AddAdminToPolicy("finance", "payroll", "alice", []byte("alicepass"))

store.SetNamespacedFull("finance", "payroll", "salary_key", []byte("AES256..."))

store.LockBucket("finance", "payroll") err = store.UnlockBucket("finance", "payroll", "bob", []byte("bobpass")) // ErrAuthFailed — does not distinguish wrong password from unknown admin (CWE-204)

err = store.RevokeAdmin("finance", "payroll", "alice") err = store.RotateAdminWrappedDEK("finance", "payroll", "bob", []byte("bobpass"))

needs, err := store.NeedsAdminRekey("finance", "payroll")

root@kitploit:~
### LevelHSM / LevelRemote buckets```go
import (
    "github.com/agberohq/keeper/pkg/hsm"
    "github.com/agberohq/keeper/pkg/remote"
)

// SoftHSM — testing only
provider, _ := hsm.NewSoftHSM()
store.RegisterHSMProvider("secure", "keys", provider)
store.CreateBucket("secure", "keys", keeper.LevelHSM, "ops")

// Vault Transit
cfg := remote.VaultTransit("https://vault.corp:8200", vaultToken, "my-key")
cfg.TLSClientCert = "/etc/keeper/client.crt"
cfg.TLSClientKey  = "/etc/keeper/client.key"
provider, _ = remote.New(cfg)
store.RegisterHSMProvider("tenant", "secrets", provider)
store.CreateBucket("tenant", "secrets", keeper.LevelRemote, "ops")

Audit-Schlüsselexport```go

// Export the audit encryption key to allow a third-party auditor to decrypt // event details without access to the master passphrase. auditKey, err := store.ExportAuditKey() defer zero.Bytes(auditKey)

events, err := auditStore.LoadChain("vault", "system", auditKey)

root@kitploit:~
### Schlüsselrotation```go
// Rotate passphrase — crash-safe WAL, resumes on next Unlock if interrupted
store.Rotate([]byte("new-passphrase"))

// Rotate KDF salt — re-derives master key, re-encrypts LevelPasswordOnly
store.RotateSalt([]byte("current-passphrase"))

Compare-and-swap```go

err := store.CompareAndSwapNamespacedFull("vault", "system", "counter", []byte("old"), []byte("new")) // ErrCASConflict if current value does not match old

root@kitploit:~
### Sicherung```go
f, _ := os.Create("keeper.db.bak")
info, err := store.Backup(f)
// info.Bytes, info.Timestamp, info.DBPath

Fehlerkatalog

Alle Sentinel-Fehler arbeiten mit errors.Is und errors.As. Stack-Traces werden zum Zeitpunkt der Erstellung über github.com/olekukonko/errors erfasst.


Sicherheitsentscheidungen

ErrAuthFailed fasst alle UnlockBucket-Fehler zusammen (CWE-204 / CVSS 5.3). Sowohl eine unbekannte Admin-ID als auch ein falsches Passwort geben ErrAuthFailed zurück. Dies verhindert die Aufzählung von Admin-IDs durch Timing- oder Fehlerzeichenfolgenvergleiche. RevokeAdmin behält ErrAdminNotFound bei, da es sich um eine administrative Operation an einem bereits entsperrten Store handelt. Der konstante Zeitvergleich für das Vorhandensein der Admin-ID wird absichtlich weggelassen. Ein Angreifer, der sub-Mikrosekunden-Unterschiede bei bbolt-Bucket-Lookups messen kann, benötigt lokalen Dateisystemzugriff – an diesem Punkt kann er den Richtlinien-Bucket direkt lesen. Das Bedrohungsmodell geht davon aus, dass die Datenbankdatei kompromittiert sein könnte; Zeitabwehr gegen entfernte Aufzählung ist das primäre Anliegen.

Argon2id dominiert das Timing. Argon2id benötigt 200–500 ms auf typischer Hardware. Unterschiede nach der Ableitung sind um vier oder mehr Größenordnungen kleiner und nicht aus der Ferne messbar. Es wird keine künstliche Gleichstellung angewendet.

DEK innerhalb der CAS-Transaktionsgrenze abgerufen. CompareAndSwapNamespacedFull ruft den Bucket-DEK innerhalb der bbolt-Schreibtransaktion ab, wodurch das Fenster eliminiert wird, in dem ein gleichzeitiges Rotate den DEK zwischen Abruf und Verwendung ändern könnte.

Passphrase wird im HTTP-Handler nie als Go-String gespeichert. Alle drei Passphrase-Felder (passphrase, new_passphrase) werden aus JSON direkt in []byte durch Roh-Map-Extraktion dekodiert, wobei das String-Backing-Array vom langlebigen Heap ferngehalten wird. Die []byte-Kopie wird nach der Verwendung mit wipeBytes genullt.

Kein --passphrase-Flag in der CLI. Flags erscheinen in der ps-Ausgabe und der Shell-Historie. Die CLI akzeptiert die Passphrase nur aus der Umgebungsvariablen KEEPER_PASSPHRASE oder einer interaktiven Echo-freien Eingabeaufforderung.

REPL-Geheimwerte sind nie sichtbar. set <key> in der REPL ohne Inline-Wert verwendet term.ReadPassword – es erscheint nicht im Terminal-Scrollback, der Shell-Historie oder ps. Ein Inline-Wert (set key value) kann für nicht sensible Daten bei Bedarf angegeben werden.

SaltStore ist absichtlich unverschlüsselt. Das KDF-Salt muss vor UnlockDatabase lesbar sein, um den Master-Schlüssel abzuleiten. policyEncKey (der für alle anderen Metadatenverschlüsselungen verwendet wird) wird selbst aus dem Master-Schlüssel abgeleitet – das Verschlüsseln des Salts mit policyEncKey wäre zirkulär. Ein KDF-Salt stellt Einzigartigkeit, nicht Vertraulichkeit sicher; es gibt keinen Sicherheitswert darin, es zu verschlüsseln.

Richtlinien-Bucket-Schlüssel werden gehasht, nicht als Klartext gespeichert. On-Disk-Richtlinien-Schlüssel sind hex(SHA-256("scheme:namespace"))[:32] – 128 Bit Schlüsselraum – anstelle von lesbaren Zeichenfolgen. Ein Offline-Angreifer, der die bbolt-Datei liest, kann keine Bucket-Namen aufzählen, ohne die Richtlinien-Blobs zu entschlüsseln.

Metadatenverschlüsselung verwendet dieselbe Chiffre-Schnittstelle wie Geheimnisse. Alle policyEncKey- und auditEncKey-Operationen laufen über s.config.NewCipher(key) – dieselbe crypt.Cipher-Schnittstelle, die für Geheimwerte konfiguriert ist. Die vom Benutzer gewählte Chiffre (AES-256-GCM für FIPS 140, standardmäßig XChaCha20-Poly1305) fließt automatisch in Richtlinien-, WAL- und Audit-Verschlüsselung ein. Kein Codepfad kodiert einen bestimmten Algorithmus fest.

LevelHSM- und LevelRemote-Buckets werden bei der Master-Schlüsselrotation übersprungen. reencryptAllWithKey und RotateSalt überspringen diese Buckets explizit. Der DEK wird vom Anbieter gesteuert; die Rotation des Master-Salts hat keine Auswirkungen darauf.

Absturzsichere Rotation mit WrappedOldKey. Rotate schreibt ein WAL, bevor es einen Datensatz berührt. Das WAL trägt WrappedOldKey: den vor der Rotation mit dem neuen Master-Schlüssel verschlüsselten Master-Schlüssel. Nach einem Absturz entschlüsselt UnlockDatabase WrappedOldKey mit dem verifizierten neuen Schlüssel und setzt die Rotation ab dem Cursor fort.


Abhängigkeiten

Tool herunterladen
Vollständige Kette + entschlüsselt Scheme/Namespace/Details
BetreiberMaster-PassphraseAlles
MethodePfadBeschreibung
POST{prefix}/unlockEntsperren des Tresors mit einer Passphrase
POST{prefix}/lockSperren des Tresors
GET{prefix}/statusSperrzustand – sicher ohne Authentifizierung abzufragen
GET{prefix}/keysAlle geheimen Schlüssel auflisten
GET{prefix}/keys/{key}Einen geheimen Wert abrufen
POST{prefix}/keysEin Geheimnis speichern (JSON oder Multipart)
DELETE{prefix}/keys/{key}Ein Geheimnis löschen
POST{prefix}/rotateMaster-Passphrase rotieren
POST{prefix}/rotate/saltKDF-Salz rotieren
GET{prefix}/backupEine Datenbank-Snapshot streamen
ErrorBedeutung
ErrStoreLockedVorgang versucht, während der Store gesperrt ist
ErrInvalidPassphraseFalsche Master-Passphrase
ErrAuthFailedJeder UnlockBucket-Fehler — unterscheidet nicht zwischen falschem Passwort und unbekannter Admin-ID (CWE-204)
ErrKeyNotFoundGeheimer Schlüssel existiert nicht
ErrBucketLockedBucket wurde nicht entsperrt
ErrPolicyImmutableZweite Richtlinie für einen vorhandenen Bucket
ErrPolicyNotFoundKeine Richtlinie für das angegebene Schema/Namespace
ErrAdminNotFoundAdmin-ID nicht in der Richtlinie — nur für RevokeAdmin
ErrHSMProviderNilHSM/Remote-Bucket ohne registrierten Anbieter erstellt
ErrCheckLatencyDB-Lese-Latenz überschritt DBLatencyThreshold
ErrCASConflictAktueller Wert entspricht nicht dem erwarteten Wert bei CompareAndSwap
ErrSecurityDowngradeBucket-übergreifende Verschiebung von höherer zu niedrigerer Sicherheitsstufe
ErrAlreadyUnlockedUnlockDatabase auf einem bereits entsperrten Store aufgerufen
ErrMasterRequiredUnlockDatabase mit nil oder zerstörtem Master aufgerufen
ErrChainBrokenÜberprüfung der Integrität der Audit-Kette fehlgeschlagen
ErrMetadataDecryptVerschlüsselte Metadaten konnten nicht entschlüsselt werden
ErrPolicySignatureRichtlinien-HMAC-Überprüfung fehlgeschlagen — Datensatz wurde manipuliert
PaketZweck
go.etcd.io/bboltEingebetteter Schlüssel-Wert-Speicher
golang.org/x/cryptoArgon2id, XChaCha20-Poly1305, HKDF, scrypt
github.com/awnumar/memguardSpeichersichere Schlüsselenklave (Master-Schlüssel, DEKs)
github.com/vmihailenco/msgpack/v5Binäre Serialisierung für Geheimnisse und Richtlinien
github.com/olekukonko/jackProzessüberwachung (optionale Jack-Integration)
github.com/olekukonko/llStrukturierte Protokollierung
github.com/olekukonko/errorsSentinel-Fehler mit Stack-Traces
github.com/olekukonko/zeroSicheres Nullsetzen von Byte-Slices
github.com/olekukonko/prompterEcho-freie Terminal-Eingabeaufforderungen (nur CLI)
github.com/integrii/flaggyCLI-Flag-Parsing (nur cmd/keeper)
golang.org/x/termTTY-Erkennung und rohes Passwort-Lesen (nur CLI)