
Simple Secure Keeper for Secrets
keeper Go के लिए एक क्रिप्टोग्राफिक गुप्त भंडार है। यह Argon2id कुंजी व्युत्पत्ति और XChaCha20-Poly1305 (डिफ़ॉल्ट) प्रमाणित एन्क्रिप्शन का उपयोग करके आराम से मनमाने बाइट पेलोड को एन्क्रिप्ट करता है, और उन्हें एक एम्बेडेड bbolt डेटाबेस में संग्रहीत करता है।
यह तीन चीजों के रूप में आता है जिनका आप स्वतंत्र रूप से उपयोग कर सकते हैं:
x/keephandler) — एक कॉल में किसी भी net/http mux पर keeper एंडपॉइंट्स माउंट करें, प्लग करने योग्य हुक, गार्ड, और एक्सेस कंट्रोल और ऑडिट लॉगिंग के लिए रिस्पॉन्स एन्कोडर के साथ।cmd/keeper) — एक निरंतर REPL सत्र, कोई-इको गुप्त प्रविष्टि, और शून्य शेल-इतिहास एक्सपोज़र के साथ एक टर्मिनल इंटरफ़ेस।keeper को Agbero लोड बैलेंसर के लिए मौलिक गुप्त प्रबंधन परत के रूप में डिज़ाइन किया गया था, लेकिन इसका Agbero पर कोई निर्भरता नहीं है और यह किसी भी Go प्रोजेक्ट में काम करता है।
keeper रहस्यों को बकेट में विभाजित करता है। प्रत्येक बकेट में एक अपरिवर्तनीय BucketSecurityPolicy होता है जो यह नियंत्रित करता है कि इसकी डेटा एन्क्रिप्शन कुंजी (DEK) कैसे सुरक्षित है। चार स्तर उपलब्ध हैं।
एक योजना एक URI उपसर्ग है जो संबंधित बकेट को समूहित करता है (vault://, certs://, space://, या कोई भी नाम जो आप पंजीकृत करते हैं)। एक सुरक्षा स्तर निर्माण के समय निर्धारित बकेट नीति की एक संपत्ति है और उसके बाद अपरिवर्तनीय है।
आप एक ही योजना के भीतर स्वतंत्र रूप से सुरक्षा स्तरों को मिश्रित कर सकते हैं। उदाहरण के लिए, vault://system LevelPasswordOnly (स्टार्टअप पर स्वचालित रूप से अनलॉक) हो सकता है, जबकि vault://admin LevelAdminWrapped (स्पष्ट क्रेडेंशियल की आवश्यकता) है।
बकेट DEK मास्टर कुंजी से HKDF-SHA256 का उपयोग करके प्रति बकेट एक डोमेन-पृथक जानकारी स्ट्रिंग (keeper-bucket-dek-v1:scheme:namespace) के साथ व्युत्पन्न होता है। सभी LevelPasswordOnly बकेट स्वचालित रूप से अनलॉक हो जाते हैं जब UnlockDatabase को सही मास्टर पासफ़्रेज़ के साथ कॉल किया जाता है। रनटाइम पर किसी प्रति-बकेट क्रेडेंशियल की आवश्यकता नहीं होती है। यह स्तर उन रहस्यों के लिए उपयुक्त है जिनकी प्रक्रिया को मानवीय हस्तक्षेप के बिना स्टार्टअप पर आवश्यकता होती है।
बकेट में उस बकेट के लिए अद्वितीय एक यादृच्छिक रूप से उत्पन्न 32-बाइट DEK है। DEK कभी भी सादे पाठ में संग्रहीत नहीं होता है। प्रत्येक अधिकृत व्यवस्थापक के लिए एक कुंजी एन्क्रिप्शन कुंजी (KEK) HKDF(masterKey‖adminCred, dekSalt) से व्युत्पन्न होती है और DEK को XChaCha20-Poly1305 के माध्यम से लपेटने के लिए उपयोग की जाती है। बकेट तब तक दुर्गम है जब तक कोई व्यवस्थापक अपने क्रेडेंशियल के साथ UnlockBucket को कॉल नहीं करता। अकेला मास्टर पासफ़्रेज़ बकेट को डिक्रिप्ट नहीं कर सकता। एक व्यवस्थापक को रद्द करने से किसी अन्य व्यवस्थापक की लिपटी प्रतिलिपि प्रभावित नहीं होती है।
बकेट DEK CreateBucket समय पर उत्पन्न होता है और तुरंत कॉलर-आपूर्ति किए गए HSMProvider द्वारा लपेटा जाता है। प्रदाता लपेटने और खोलने के संचालन करता है — keeper प्रदाता को DEK सौंपने के बाद कच्चे DEK को कभी संभाल नहीं करता है। UnlockDatabase स्वचालित रूप से सभी पंजीकृत HSM बकेट के लिए प्रदाता को Envelope को खोलने और बीजने के लिए कॉल करता है। मास्टर कुंजी रोटेशन इन बकेट को पुनः एन्क्रिप्ट नहीं करता है; DEK प्रदाता-नियंत्रित होता है।
एक अंतर्निहित SoftHSM कार्यान्वयन जो मेमगार्ड-संरक्षित रैपिंग कुंजी द्वारा समर्थित है, परीक्षण और CI वातावरण के लिए pkg/hsm में उपलब्ध है। इसे उत्पादन में उपयोग न करें।
कुंजी प्रबंधन व्यवहार में LevelHSM के समान, लेकिन HSMProvider को pkg/remote.Provider द्वारा कार्यान्वित किया गया है — एक कॉन्फ़िगरेबल HTTPS एडाप्टर जो TLS पर किसी भी दूरस्थ KMS सेवा को लपेटने और खोलने का काम सौंपता है। HashiCorp Vault Transit, AWS KMS, और GCP Cloud KMS के लिए पूर्व-निर्मित कॉन्फ़िगरेशन pkg/remote में प्रदान किए गए हैं। उत्पादन उपयोग के लिए, पारस्परिक TLS प्रमाणीकरण सक्षम करने के लिए TLSClientCert और TLSClientKey कॉन्फ़िगर करें।
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
पहली व्युत्पत्ति पर एक सत्यापन हैश संग्रहीत किया जाता है:```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes
बाद के DeriveMaster कॉल इस हैश को पुनः गणना करते हैं और इसकी तुलना crypto/subtle.ConstantTimeCompare से करते हैं। बेमेल होने पर ErrInvalidPassphrase वापस आता है।
KDF सॉल्ट डिज़ाइन के अनुसार अनएन्क्रिप्टेड रूप से संग्रहीत किया जाता है। मास्टर कुंजी प्राप्त करने के लिए UnlockDatabase से पहले इसे पढ़ने योग्य होना चाहिए — इसे मास्टर से प्राप्त कुंजी के साथ एन्क्रिप्ट करना चक्रीय होगा। KDF सॉल्ट कोई गुप्त जानकारी नहीं है; इसका उद्देश्य विशिष्टता है, गोपनीयता नहीं।
प्रत्येक प्लेनटेक्स्ट मान को बकेट DEK का उपयोग करके XChaCha20-Poly1305 के साथ एन्क्रिप्ट किया जाता है:``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
संग्रहीत रिकॉर्ड एक msgpack-एन्कोडेड `Secret` संरचना है जिसमें सिफरटेक्स्ट, एन्क्रिप्टेड मेटाडेटा और स्कीमा संस्करण शामिल हैं। प्रमाणीकरण अंतर्निहित है: गलत कुंजी से डिक्रिप्ट किया गया सिफरटेक्स्ट कोई भी प्लेनटेक्स्ट वापस करने से पहले AEAD प्रमाणीकरण विफलता उत्पन्न करता है।
### KEK व्युत्पत्ति — 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)
KEK को HKDF का उपयोग करके प्राप्त किया जाता है, न कि दूसरे Argon2 पास का। मास्टर कुंजी पहले से ही एक उच्च-लागत वाले KDF द्वारा उत्पन्न की गई थी; दूसरे Argon2 आह्वान से प्रत्येक UnlockBucket कॉल में सैकड़ों मिलीसेकंड की विलंबता बढ़ जाएगी, जबकि कोई सुरक्षा लाभ नहीं होगा। HKDF-SHA256 लगभग एक माइक्रोसेकंड में काम करता है।
गहराई में सुरक्षा: एक हमलावर जो केवल डेटाबेस से समझौता करता है, उसे लिपटा हुआ DEK और HKDF नमक मिलता है, लेकिन मास्टर कुंजी के बिना KEK प्राप्त नहीं कर सकता। एक हमलावर जो केवल मास्टर कुंजी से समझौता करता है, वह प्रशासनिक क्रेडेंशियल जाने बिना किसी भी LevelAdminWrapped DEK को अनरैप नहीं कर सकता।
गुप्त मेटाडेटा (निर्माण समय, अद्यतन समय, पहुँच गणना, संस्करण) को सिफरटेक्स्ट से अलग एन्क्रिप्ट किया जाता है:``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
For `LevelAdminWrapped`, `LevelHSM`, और `LevelRemote` बकेट के लिए इसका मतलब है कि मेटाडेटा बकेट क्रेडेंशियल के बिना अप्राप्य है, जिससे डेटाबेस फ़ाइल तक पढ़ने की पहुँच वाला हमलावर एक्सेस पैटर्न या टाइमस्टैम्प नहीं सीख सकता।
**टाइमिंग साइड-चैनल नोट:** XChaCha20-Poly1305 प्रमाणीकरण त्रुटि लौटाने से पहले पूर्ण सिफरटेक्स्ट प्रोसेस करता है। फ़ॉलबैक डिक्रिप्ट पथ (नया व्युत्पन्न DEK → पुराना मास्टर-की-एज़-DEK) कोई फर्क नहीं पड़ता कि कौन सी कुंजी सफल होती है, समान वॉल-क्लॉक समय लेता है। कोई टाइमिंग साइड-चैनल किसी रिकॉर्ड की माइग्रेशन स्थिति को लीक नहीं करता।
### मेटाडेटा एन्क्रिप्शन — पॉलिसीज़, WAL, और ऑडिट
सभी संरचनात्मक मेटाडेटा को भी रेस्ट पर एन्क्रिप्ट किया गया है। `UnlockDatabase` समय पर मास्टर की से दो कुंजियाँ व्युत्पन्न की जाती हैं:```
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 एन्क्रिप्ट करता है: BucketSecurityPolicy मान और रोटेशन WAL।
auditEncKey एन्क्रिप्ट करता है: प्रत्येक ऑडिट घटना के Scheme, Namespace, और Details फ़ील्ड।
दोनों कुंजियाँ Lock() पर मेमोरी से साफ़ कर दी जाती हैं। मेटाडेटा एन्क्रिप्शन के लिए उपयोग किया जाने वाला सिफर वही कॉन्फ़िगरेबल crypt.Cipher इंटरफ़ेस है जो सीक्रेट्स के लिए उपयोग किया जाता है — उपयोगकर्ता का सिफर चयन (FIPS के लिए AES-256-GCM, डिफ़ॉल्ट रूप से XChaCha20-Poly1305) स्वचालित रूप से प्रवाहित होता है।
सभी एन्क्रिप्टेड मेटाडेटा ब्लॉब्स के लिए वायर फ़ॉर्मेट:``` nonce (cipher.NonceSize() bytes) || AEAD-ciphertext
### नीति बकेट कुंजी हैशिंग
ऑन-डिस्क नीति कुंजियाँ सादे पाठ `scheme:namespace` के बजाय अपारदर्शी हैश होती हैं
स्ट्रिंग, जो बकेट नामों की ऑफ़लाइन गणना को रोकती हैं:```
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)
मेमोरी में schemeRegistry "scheme:namespace" को अपनी कुंजी के रूप में उपयोग करता रहता है — केवल डिस्क पर प्रतिनिधित्व बदलता है।
प्रत्येक नीति रिकॉर्ड में दो अखंडता टैग होते हैं, जो एक bbolt लेन-देन में परमाणु रूप से लिखे जाते हैं:``` 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
Before `UnlockDatabase`, only the SHA-256 hash is available. After unlock,
`loadPolicy` verifies the HMAC tag. `UnlockDatabase` calls `upgradePolicyHMACs`
to backfill HMAC tags on policies created before this feature existed.
### ऑडिट HMAC हस्ताक्षर```
auditKey ← HKDF-SHA256(masterKey, nil, info="keeper-audit-hmac-v1") → 32 bytes
HMAC ← HMAC-SHA256(auditKey, event fields including Seq)
हस्ताक्षर कुंजी UnlockDatabase पर सक्रिय होती है और Lock पर साफ़ की जाती है। जब मास्टर कुंजी घुमाई जाती है, तो Rotate प्रत्येक सक्रिय ऑडिट श्रृंखला में एक कुंजी-रोटेशन चेकपॉइंट इवेंट जोड़ता है, जो पुरानी ऑडिट कुंजी के साथ पुराने युग के अंतिम इवेंट के रूप में हस्ताक्षरित होता है। इतिहास को कभी भी पुनः लिखा नहीं जाता; चेकपॉइंट युगों के बीच का विश्वास सेतु है।
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)
सभी मध्यवर्ती कुंजियाँ उपयोग के तुरंत बाद शून्य कर दी जाती हैं। मास्टर कुंजी कभी भी डिस्क पर किसी भी रूप में नहीं लिखी जाती है।
---
## भंडारण योजना
अंतर्निहित डेटाबेस bbolt है। सभी बकेट और उनकी सामग्री:
| bbolt बकेट | कुंजी | मान |
|---|---|---|
| `__meta__` | `salt` | msgpack — SaltStore (अनएन्क्रिप्टेड; यदि एन्क्रिप्ट किया गया तो चक्रीय निर्भरता) |
| `__meta__` | `verify` | raw bytes — Argon2id सत्यापन हैश |
| `__meta__` | `rotation_wal` | `nonce‖AEAD(msgpack(RotationWAL))` |
| `__meta__` | `bucket_dek_done` | `"1"` — DEK माइग्रेशन पूर्णता मार्कर |
| `__policies__` | `hex(SHA-256(scheme:ns))[:32]` | `nonce‖AEAD(msgpack(BucketSecurityPolicy))` |
| `__policies__` | `<base>__hash__` | hex SHA-256 एन्क्रिप्टेड पॉलिसी बाइट्स का |
| `__policies__` | `<base>__hmac__` | hex HMAC-SHA256(policyKey, encrypted policy bytes) |
| `__audit__/scheme/namespace` | event UUID | JSON — ऑडिट इवेंट |
| `__audit__/scheme/namespace` | `__chain_index__` | JSON — chainIndex |
| `scheme/namespace` | key string | msgpack — गुप्त संरचना |
### गुप्त संरचना (msgpack)```go
type Secret struct {
Ciphertext []byte `msgpack:"ct"`
EncryptedMeta []byte `msgpack:"em,omitempty"`
SchemaVersion int `msgpack:"sv"` // always 1
}
Event स्ट्रक्चर अलग प्लेनटेक्स्ट रूटिंग फ़ील्ड (Scheme, Namespace) का उपयोग करता है, साथ ही एन्क्रिप्टेड पेलोड फ़ील्ड (EncScheme, EncNamespace, EncDetails) का। प्लेनटेक्स्ट रूटिंग फ़ील्ड और एन्क्रिप्टेड EncDetails बाइट्स पर चेकसम की गणना की जाती है, ताकि बिना किसी कुंजी के तीन स्तरों पर श्रृंखला अखंडता सत्यापित की जा सके:
| स्तर | पास क्या है | क्या सत्यापित कर सकता है |
|---|---|---|
| सार्वजनिक | कुछ नहीं | SHA-256 चेकसम श्रृंखला (छेड़छाड़ और सम्मिलन का पता लगाता है) |
| ऑडिट-कुंजी धारक |
उदाहरण: एक अनुपालन ऑडिटर को केवल auditEncKey प्राप्त होता है। वे कुंजी रोटेशन के दौरान पूर्ण HMAC श्रृंखला को सत्यापित कर सकते हैं और सभी इवेंट विवरण पढ़ सकते हैं, लेकिन किसी भी गुप्त मान को डिक्रिप्ट नहीं कर सकते। केवल डेटाबेस फ़ाइल वाला एक सार्वजनिक पर्यवेक्षक अभी भी यह पता लगा सकता है कि क्या कोई इवेंट बाद में संशोधित या सम्मिलित किया गया था।
KDF नमक को salt मेटाडेटा कुंजी के अंतर्गत msgpack-एन्कोडेड SaltStore के रूप में संग्रहीत किया जाता है। प्रत्येक नमक रोटेशन एक नया SaltEntry जोड़ता है और CurrentVersion को आगे बढ़ाता है। पुरानी प्रविष्टियाँ ऑडिट ट्रेल के रूप में बनी रहती हैं। SaltStore अनएन्क्रिप्टेड संग्रहीत किया जाता है — सुरक्षा निर्णय देखें।
Rotate किसी भी रिकॉर्ड को छूने से पहले एक WAL लिखता है। WAL WrappedOldKey रखता है: नई मास्टर कुंजी से एन्क्रिप्टेड पूर्व-रोटेशन मास्टर कुंजी। क्रैश के बाद पुराना पासफ़्रेज़ चला जाता है; WrappedOldKey ही पुरानी कुंजी को सीमा पार ले जाने का एकमात्र सही तरीका है। UnlockDatabase पर, जब WAL मौजूद होता है, नई मास्टर कुंजी WrappedOldKey को डिक्रिप्ट करती है और रोटेशन WAL कर्सर से फिर से शुरू होता है। WAL स्वयं policyEncKey से एन्क्रिप्ट किया जाता है।
प्रत्येक महत्वपूर्ण संचालन बकेट की ऑडिट श्रृंखला में एक छेड़छाड़-प्रमाण इवेंट जोड़ता है। श्रृंखला अखंडता दो तंत्रों पर निर्भर करती है।
चेकसम. prevChecksum, ID, BucketID, Scheme, Namespace, EncDetails, EventType और Timestamp पर SHA-256। Scheme/Namespace को प्लेनटेक्स्ट के रूप में उपयोग करना (हमेशा एन्क्रिप्टेड रूपों के साथ संरक्षित) यह सुनिश्चित करता है कि चेकसम लोड पथों पर स्थिर रहे। EncDetails एन्क्रिप्टेड पेलोड पर अखंडता प्रदान करता है।
HMAC. Seq सहित सभी फ़ील्ड पर HMAC-SHA256। कोई हमलावर जो डेटाबेस में लिख सकता है लेकिन ऑडिट कुंजी नहीं जानता, वैध HMAC उत्पन्न नहीं कर सकता। VerifyIntegrity प्रत्येक इवेंट के लिए दोनों परतों की जाँच करता है।
कुंजी रोटेशन युग सीमा. Rotate पर, प्रत्येक सक्रिय श्रृंखला में एक चेकपॉइंट इवेंट जोड़ा जाता है जिसमें निकास और आगामी दोनों ऑडिट कुंजियों के फिंगरप्रिंट होते हैं। चेकपॉइंट पर निकास कुंजी से हस्ताक्षर किया जाता है। किसी भी युग कुंजी वाले ऑडिटर wrapped_new_key फ़ील्ड से बाद के युग कुंजियों को पुनर्प्राप्त कर सकते हैं और पूर्ण श्रृंखला में HMAC निरंतरता सत्यापित कर सकते हैं।
स्वचालित छंटाई. जब Config में AuditPruneInterval सेट किया जाता है, एक jack.Scheduler समय-समय पर चलता है और प्रत्येक पंजीकृत बकेट पर PruneEvents कॉल करता है। इस सेटिंग के बावजूद LevelHSM और LevelRemote बकेट कभी नहीं छांटे जाते।
जैक एक वैकल्पिक प्रक्रिया पर्यवेक्षण लाइब्रेरी है। जब WithJack के माध्यम से JackConfig प्रदान किया जाता है, तो कीपर स्वचालित रूप से पृष्ठभूमि घटकों को सक्रिय करता है:
AutoLockInterval के बाद LevelAdminWrapped बकेट DEKs को हटा देता है। LevelPasswordOnly बकेट अनलॉक रहते हैं ताकि पृष्ठभूमि कार्य निर्बाध जारी रहें। लूपर टास्क के अंदर एकल-लिखित-लॉक पैटर्न पिछले डिज़ाइनों में मौजूद RUnlock→Lock रेस कंडीशन को समाप्त करता है।LevelAdminWrapped DEKs के लिए TTL-आधारित समाप्ति।jack.Doctor के साथ पंजीकृत।PruneEvents।यदि JackConfig प्रदान नहीं किया गया है, तो कीपर इन पृष्ठभूमि कार्यों के बिना चलता है। कीपर कभी pool.Shutdown नहीं कहता — पूल का जीवनचक्र कॉलर का है।
x/keepcmd किसी भी CLI फ्रेमवर्क से अलग पुन: प्रयोज्य कीपर संचालन प्रदान करता है। इसे अपने स्वयं के एप्लिकेशन में एम्बेड करें और CLI बाइनरी को खींचे बिना टाइप, परीक्षण योग्य गुप्त प्रबंधन प्राप्त करें।```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
`keepcmd` कभी भी `prompter` को कॉल नहीं करता या stdin से नहीं पढ़ता। पासफ़्रेज़ हल करना पूरी तरह से कॉल करने वाले की जिम्मेदारी है — यह पैकेज को हेडलेस सर्वर संदर्भों में सुरक्षित रखता है।
`NoClose: true` प्रत्येक ऑपरेशन के बाद `Commands` को `store.Close()` कॉल करने से रोकता है। इसे REPL / सत्र संदर्भों में उपयोग करें जहाँ एक स्टोर कई कॉलों में साझा किया जाता है।
---
## x/keephandler
`x/keephandler` किसी भी `net/http` mux पर कीपर HTTP एंडपॉइंट्स माउंट करता है। कोई बाहरी राउटर निर्भरता नहीं — यह Go 1.22+ मेथड+पैटर्न रूटिंग का उपयोग 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)
}),
)
BeforeFunc लौटाता है (allow bool, err error)।
(true, nil) — अनुरोध को आगे बढ़ने दें।(false, nil) — रद्द करें; हुक ने पहले ही पूर्ण प्रतिक्रिया लिख दी है।(false, err) — रद्द करें; फ्रेमवर्क err.Error() का उपयोग करके 500 लिखता है।
हुक ने w पर कुछ भी नहीं लिखा होना चाहिए।Hook.CaptureBody bool नियंत्रित करता है कि AfterFunc को प्रतिक्रिया निकाय प्राप्त होता है या नहीं। false (डिफ़ॉल्ट) एक हल्के statusWriter रैपर की लागत लगता है; true पूरे निकाय को AfterFunc के लिए bytes.Buffer में बफर करता है — प्रति अनुरोध एक आवंटन।
हुक पंजीकरण क्रम में निष्पादित होते हैं। एकाधिक WithHooks कॉल योगात्मक हैं। किसी दिए गए रूट नाम के लिए केवल पहले पंजीकृत हुक का उपयोग किया जाता है—उसी रूट के लिए बाद के पंजीकरण को अनदेखा किया जाता है।
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 }
`UnlockDatabase` निम्नलिखित क्रम में कार्य करता है:
1. ऑडिट HMAC हस्ताक्षर कुंजी व्युत्पन्न और सक्रिय करता है
2. नीति HMAC कुंजी व्युत्पन्न और सक्रिय करता है
3. `policyEncKey` और `auditEncKey` को व्युत्पन्न और सक्रिय करता है
4. `schemeRegistry` को साफ़ और पुनः लोड करता है (सभी नीति ब्लॉब्स को डिक्रिप्ट करता है)
5. किसी भी बाधित रोटेशन WAL को पुनः आरंभ करता है
6. नीति HMAC टैग को अपग्रेड करता है
7. सभी `LevelPasswordOnly` बकेट DEKs को Envelope में सीड करता है
8. पृष्ठभूमि कार्य शुरू करता है (माइग्रेशन लूपर, ऑटो-लॉक, हेल्थ पेशेंट)
### LevelPasswordOnly बकेट — पूर्ण जीवनचक्र```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")
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")
### LevelHSM / LevelRemote बकेट```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")
// 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)
### कुंजी घुमाव```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"))
err := store.CompareAndSwapNamespacedFull("vault", "system", "counter", []byte("old"), []byte("new")) // ErrCASConflict if current value does not match old
### बैकअप```go
f, _ := os.Create("keeper.db.bak")
info, err := store.Backup(f)
// info.Bytes, info.Timestamp, info.DBPath
सभी संतरी त्रुटियाँ errors.Is और errors.As के साथ काम करती हैं। स्टैक ट्रेस को
github.com/olekukonko/errors के माध्यम से निर्माण के बिंदु पर कैप्चर किया जाता है।
ErrAuthFailed सभी UnlockBucket विफलताओं को एकीकृत करता है (CWE-204 / CVSS 5.3)। अज्ञात व्यवस्थापक आईडी और गलत पासवर्ड दोनों ErrAuthFailed लौटाते हैं। यह समय या त्रुटि-स्ट्रिंग तुलना द्वारा व्यवस्थापक आईडी गणना को रोकता है। RevokeAdmin ErrAdminNotFound को बनाए रखता है क्योंकि यह पहले से अनलॉक किए गए स्टोर पर एक प्रशासनिक कार्रवाई है। व्यवस्थापक आईडी उपस्थिति के लिए स्थिर-समय तुलना को जानबूझकर छोड़ दिया गया है। कोई हमलावर जो bbolt बकेट लुकअप में सब-माइक्रोसेकंड अंतर माप सकता है, उसे स्थानीय फ़ाइल सिस्टम तक पहुँच की आवश्यकता होगी — जिस बिंदु पर वे सीधे नीति बकेट पढ़ सकते हैं। खतरा मॉडल मानता है कि डेटाबेस फ़ाइल से समझौता हो सकता है; रिमोट गणना के खिलाफ समय रक्षा प्राथमिक चिंता है।
Argon2id समय पर हावी है। Argon2id सामान्य हार्डवेयर पर 200–500 ms लेता है। पोस्ट-डेरिवेशन तुलना अंतर चार या अधिक परिमाण के क्रम छोटे होते हैं और दूर से मापने योग्य नहीं होते। कोई कृत्रिम समीकरण लागू नहीं किया गया है।
DEK CAS लेन-देन सीमा के अंदर प्राप्त किया जाता है। CompareAndSwapNamespacedFull bbolt लेखन लेन-देन के अंदर बकेट DEK प्राप्त करता है, उस विंडो को समाप्त करता है जहाँ एक समवर्ती Rotate प्राप्ति और उपयोग के बीच DEK बदल सकता है।
पासफ्रेज़ HTTP हैंडलर में कभी भी Go स्ट्रिंग के रूप में संग्रहीत नहीं किया जाता है। तीनों पासफ्रेज़ फ़ील्ड (passphrase, new_passphrase) को JSON से सीधे []byte में रॉ-मैप निष्कर्षण के माध्यम से डीकोड किया जाता है, स्ट्रिंग बैकिंग ऐरे को दीर्घजीवी ढेर से दूर रखते हुए। []byte कॉपी को उपयोग के बाद wipeBytes से शून्य किया जाता है।
CLI में कोई --passphrase फ़्लैग नहीं। फ़्लैग ps आउटपुट और शेल इतिहास में दिखाई देते हैं। CLI पासफ्रेज़ केवल KEEPER_PASSPHRASE एनवी या इंटरैक्टिव नो-एको प्रॉम्प्ट से स्वीकार करता है।
REPL गुप्त मान कभी दृश्य नहीं होते। REPL में इनलाइन मान के बिना set <key> term.ReadPassword का उपयोग करता है — यह टर्मिनल स्क्रॉलबैक, शेल इतिहास या ps में दिखाई नहीं देता। गैर-संवेदनशील डेटा के लिए सुविधा होने पर एक इनलाइन मान (set key value) प्रदान किया जा सकता है।
SaltStore जानबूझकर अनएन्क्रिप्टेड है। UnlockDatabase से पहले मास्टर कुंजी प्राप्त करने के लिए KDF साल्ट पढ़ने योग्य होना चाहिए। policyEncKey (अन्य सभी मेटाडेटा एन्क्रिप्शन के लिए उपयोग किया जाता है) स्वयं मास्टर कुंजी से व्युत्पन्न होता है — साल्ट को policyEncKey से एन्क्रिप्ट करना चक्रीय होगा। KDF साल्ट अद्वितीयता प्रदान करता है, गोपनीयता नहीं; इसे एन्क्रिप्ट करने का कोई सुरक्षा मूल्य नहीं है।
नीति बकेट कुंजियाँ हैश की जाती हैं, सादा पाठ नहीं। डिस्क पर नीति कुंजियाँ hex(SHA-256("स्कीम:नेमस्पेस"))[:32] हैं — कुंजी स्पेस के 128 बिट — पठनीय स्ट्रिंग के बजाय। bbolt फ़ाइल पढ़ने वाला ऑफ़लाइन हमलावर नीति ब्लॉब को डिक्रिप्ट किए बिना बकेट नामों की गणना नहीं कर सकता।
मेटाडेटा एन्क्रिप्शन गुप्त के समान सिफर इंटरफ़ेस का उपयोग करता है। सभी policyEncKey और auditEncKey ऑपरेशन s.config.NewCipher(key) के माध्यम से जाते हैं — वही crypt.Cipher इंटरफ़ेस जो गुप्त मानों के लिए कॉन्फ़िगर किया गया है। उपयोगकर्ता का सिफर चयन (FIPS 140 के लिए AES-256-GCM, डिफ़ॉल्ट रूप से XChaCha20-Poly1305) स्वचालित रूप से नीति, WAL और ऑडिट एन्क्रिप्शन में प्रवाहित होता है। कोई कोड पथ किसी विशिष्ट एल्गोरिदम को हार्ड-कोड नहीं करता है।
LevelHSM और LevelRemote बकेट मास्टर कुंजी रोटेशन के दौरान छोड़ दिए जाते हैं। reencryptAllWithKey और RotateSalt स्पष्ट रूप से इन बकेट को छोड़ देते हैं। DEK प्रदाता-नियंत्रित है; मास्टर साल्ट रोटेशन इसे प्रभावित नहीं करता है।
WrappedOldKey के साथ क्रैश-सुरक्षित रोटेशन। Rotate किसी भी रिकॉर्ड को छूने से पहले एक WAL लिखता है। WAL WrappedOldKey ले जाता है: नई मास्टर कुंजी के साथ एन्क्रिप्ट की गई पूर्व-रोटेशन मास्टर कुंजी। क्रैश के बाद, UnlockDatabase सत्यापित नई कुंजी का उपयोग करके WrappedOldKey को डिक्रिप्ट करता है और कर्सर से रोटेशन फिर से शुरू करता है।
auditEncKey| पूर्ण श्रृंखला + Scheme/Namespace/Details को डिक्रिप्ट करें |
| संचालक | मास्टर पासफ़्रेज़ | सब कुछ |
| विधि | पथ | विवरण |
|---|
POST | {prefix}/unlock | पासफ्रेज़ के साथ स्टोर को अनलॉक करें |
POST | {prefix}/lock | स्टोर को लॉक करें |
GET | {prefix}/status | लॉक स्थिति — प्रमाणीकरण के बिना पोल करना सुरक्षित |
GET | {prefix}/keys | सभी गुप्त कुंजियाँ सूचीबद्ध करें |
GET | {prefix}/keys/{key} | एक गुप्त मान प्राप्त करें |
POST | {prefix}/keys | एक गुप्त मान संग्रहीत करें (JSON या मल्टीपार्ट) |
DELETE | {prefix}/keys/{key} | एक गुप्त मान हटाएँ |
POST | {prefix}/rotate | मास्टर पासफ्रेज़ को घुमाएँ |
POST | {prefix}/rotate/salt | KDF नमक को घुमाएँ |
GET | {prefix}/backup | डेटाबेस स्नैपशॉट स्ट्रीम करें |
| त्रुटि | अर्थ |
|---|
ErrStoreLocked | स्टोर लॉक होने पर कार्रवाई का प्रयास किया गया |
ErrInvalidPassphrase | गलत मास्टर पासफ्रेज़ |
ErrAuthFailed | कोई भी UnlockBucket विफलता — गलत पासवर्ड या अज्ञात व्यवस्थापक आईडी के बीच अंतर नहीं करता (CWE-204) |
ErrKeyNotFound | गुप्त कुंजी मौजूद नहीं है |
ErrBucketLocked | बकेट अनलॉक नहीं किया गया है |
ErrPolicyImmutable | मौजूदा बकेट के लिए दूसरी नीति |
ErrPolicyNotFound | दिए गए स्कीम/नेमस्पेस के लिए कोई नीति नहीं |
ErrAdminNotFound | व्यवस्थापक आईडी नीति में नहीं — केवल RevokeAdmin |
ErrHSMProviderNil | पंजीकृत प्रदाता के बिना बनाया गया HSM/रिमोट बकेट |
ErrCheckLatency | DB पढ़ने की विलंबता DBLatencyThreshold से अधिक हो गई |
ErrCASConflict | CompareAndSwap में वर्तमान मान अपेक्षित से मेल नहीं खाता |
ErrSecurityDowngrade | उच्च से निम्न सुरक्षा स्तर पर क्रॉस-बकेट स्थानांतरण |
ErrAlreadyUnlocked | पहले से अनलॉक किए गए स्टोर पर UnlockDatabase कॉल किया गया |
ErrMasterRequired | शून्य या नष्ट किए गए Master के साथ UnlockDatabase कॉल किया गया |
ErrChainBroken | ऑडिट श्रृंखला अखंडता सत्यापन विफल |
ErrMetadataDecrypt | एन्क्रिप्टेड मेटाडेटा को डिक्रिप्ट नहीं किया जा सका |
ErrPolicySignature | नीति HMAC सत्यापन विफल — रिकॉर्ड के साथ छेड़छाड़ की गई |
| पैकेज | उद्देश्य |
|---|
go.etcd.io/bbolt | एम्बेडेड कुंजी-मूल्य स्टोर |
golang.org/x/crypto | Argon2id, XChaCha20-Poly1305, HKDF, scrypt |
github.com/awnumar/memguard | मेमोरी-सुरक्षित कुंजी एन्क्लेव (मास्टर कुंजी, DEK) |
github.com/vmihailenco/msgpack/v5 | गुप्त और नीतियों के लिए बाइनरी क्रमांकन |
github.com/olekukonko/jack | प्रक्रिया पर्यवेक्षण (वैकल्पिक Jack एकीकरण) |
github.com/olekukonko/ll | संरचित लॉगिंग |
github.com/olekukonko/errors | स्टैक ट्रेस के साथ संतरी त्रुटियाँ |
github.com/olekukonko/zero | सुरक्षित बाइट-स्लाइस शून्यीकरण |
github.com/olekukonko/prompter | नो-एको टर्मिनल प्रॉम्प्ट (केवल CLI) |
github.com/integrii/flaggy | CLI फ़्लैग पार्सिंग (केवल cmd/keeper) |
golang.org/x/term | TTY डिटेक्शन और रॉ पासवर्ड पढ़ना (केवल CLI) |