
Semplice gestore sicuro di segreti
Keeper è uno store crittografico di segreti per Go. Cripta payload arbitrari di byte a riposo utilizzando la derivazione delle chiavi Argon2id e la crittografia autenticata XChaCha20-Poly1305 (predefinita) e li memorizza in un database bbolt embedded.
Viene fornito come tre componenti che puoi usare indipendentemente:
x/keephandler) — monta gli endpoint di keeper su qualsiasi mux net/http con una singola chiamata, con hook, guardie e codificatori di risposta inseribili per il controllo degli accessi e la registrazione di audit.cmd/keeper) — un terminale con sessione REPL persistente, immissione di segreti senza eco e nessuna esposizione della cronologia della shell.Keeper è stato progettato come livello fondamentale di gestione dei segreti per il load balancer Agbero ma non ha dipendenze da Agbero e funziona in qualsiasi progetto Go.
Keeper partiziona i segreti in bucket. Ogni bucket ha un BucketSecurityPolicy immutabile che regola come viene protetta la sua Chiave di Crittografia dei Dati (DEK). Sono disponibili quattro livelli.
Uno schema è un prefisso URI che raggruppa bucket correlati (vault://, certs://, space:// o qualsiasi nome che registri). Un livello di sicurezza è una proprietà della policy del bucket impostata al momento della creazione e immutabile successivamente.
Puoi mescolare liberamente i livelli di sicurezza all'interno dello stesso schema. Ad esempio, vault://system potrebbe essere LevelPasswordOnly (sbloccato automaticamente all'avvio), mentre vault://admin è LevelAdminWrapped (richiede credenziali esplicite).
La DEK del bucket è derivata dalla chiave master usando HKDF-SHA256 con una stringa info separata per dominio per bucket (keeper-bucket-dek-v1:scheme:namespace). Tutti i bucket LevelPasswordOnly vengono sbloccati automaticamente quando viene chiamato UnlockDatabase con la corretta passphrase master. Nessuna credenziale per bucket è richiesta in fase di esecuzione. Questo livello è appropriato per segreti di cui il processo ha bisogno all'avvio senza interazione umana.
Il bucket ha una DEK casuale di 32 byte univoca per quel bucket. La DEK non viene mai memorizzata in chiaro. Per ogni amministratore autorizzato, una Chiave di Crittografia delle Chiavi (KEK) viene derivata da HKDF(masterKey‖adminCred, dekSalt) e usata per avvolgere la DEK tramite XChaCha20-Poly1305. Il bucket è inaccessibile finché un amministratore non chiama UnlockBucket con la propria credenziale. La sola passphrase master non può decifrare il bucket. La revoca di un amministratore non influisce sulla copia avvolta di nessun altro amministratore.
La DEK del bucket viene generata al momento di CreateBucket e immediatamente avvolta da un HSMProvider fornito dal chiamante. Il provider esegue le operazioni di avvolgimento e disavvolgimento — keeper non gestisce mai la DEK grezza dopo averla consegnata al provider. UnlockDatabase chiama automaticamente il provider per disavvolgere e popolare l'Envelope per tutti i bucket HSM registrati. La rotazione della chiave master non re-cripta questi bucket; la DEK è controllata dal provider.
Un'implementazione SoftHSM integrata, basata su una chiave di avvolgimento protetta da memguard, è disponibile in pkg/hsm per test e ambienti CI. Non utilizzarla in produzione.
Identico a LevelHSM per quanto riguarda la gestione delle chiavi, ma HSMProvider è implementato da pkg/remote.Provider — un adattatore HTTPS configurabile che delega le operazioni di avvolgimento e disavvolgimento a qualsiasi servizio KMS remoto su TLS. Le configurazioni predefinite per HashiCorp Vault Transit, AWS KMS e GCP Cloud KMS sono fornite in pkg/remote. Per uso in produzione, configura TLSClientCert e TLSClientKey per abilitare l'autenticazione TLS reciproca.
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
Un hash di verifica viene memorizzato alla prima derivazione:```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes
Subsequent DeriveMaster calls recompute this hash and compare it with
crypto/subtle.ConstantTimeCompare. A mismatch returns ErrInvalidPassphrase.
The KDF salt is stored unencrypted by design. It must be readable before
UnlockDatabase to derive the master key — encrypting it with a key derived
from the master would be circular. A KDF salt is not a secret; its purpose is
uniqueness, not confidentiality.
Ogni valore in chiaro viene crittografato con XChaCha20-Poly1305 utilizzando la DEK del bucket:``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
Il record memorizzato è un struct `Secret` codificato in msgpack contenente il testo cifrato, i metadati cifrati e la versione dello schema. L'autenticazione è implicita: un testo cifrato decifrato con la chiave sbagliata produce un fallimento di autenticazione AEAD prima che venga restituito qualsiasi testo in chiaro.
### 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)
Il KEK è derivato usando HKDF invece di un secondo passaggio di Argon2. La chiave master
era già stata prodotta da un KDF ad alto costo; una seconda invocazione di Argon2 aggiungerebbe
centinaia di millisecondi di latenza a ogni chiamata di UnlockBucket senza alcun
vantaggio in termini di sicurezza. HKDF-SHA256 opera in circa un microsecondo.
Difesa in profondità: Un attaccante che compromette solo il database ottiene il
DEK avvolto e il salt di HKDF, ma non può derivare il KEK senza la chiave master.
Un attaccante che compromette solo la chiave master non può disavvolgere alcun
DEK LevelAdminWrapped senza conoscere anche le credenziali dell'amministratore.
I metadati segreti (tempo di creazione, tempo di aggiornamento, conteggio degli accessi, versione) sono crittografati separatamente dal testo cifrato:``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
Per i bucket `LevelAdminWrapped`, `LevelHSM` e `LevelRemote`, ciò significa
che i metadati sono inaccessibili senza le credenziali del bucket, impedendo a un attaccante
con accesso in lettura al file del database di apprendere pattern di accesso o
timestamp.