
حافظ بسيط وآمن للأسرار
Keeper هو مخزن تشفير سري للغة Go. يقوم بتشفير أي حمولات بايت تعسفية عند التخزين باستخدام اشتقاق المفتاح Argon2id والتشفير الموثّق XChaCha20-Poly1305 (الافتراضي)، ويخزّنها في قاعدة بيانات bbolt مضمنة.
يأتي على ثلاثة أشكال يمكن استخدامها بشكل مستقل:
x/keephandler) — قم بتركيب نقاط نهاية Keeper على أي موزع net/http في استدعاء واحد، مع خطافات قابلة للتوصيل، وحراس، ومشفِّرات استجابة للتحكم في الوصول وتسجيل التدقيق.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 بكلمة المرور الرئيسية الصحيحة. لا حاجة لبيانات اعتماد لكل مجموعة في وقت التشغيل. هذا المستوى مناسب للأسرار التي تحتاجها العملية عند بدء التشغيل دون تدخل بشري.
للمجموعة DEK مولد عشوائيًا بحجم 32 بايت وفريد لتلك المجموعة. لا يُخزَّن DEK أبدًا بنص عادي. لكل مسؤول مصرح له، يتم اشتقاق مفتاح تشفير المفاتيح (KEK) من HKDF(masterKey‖adminCred, dekSalt) ويستخدم لتغليف DEK عبر XChaCha20-Poly1305. لا يمكن الوصول إلى المجموعة حتى يقوم مسؤول باستدعاء UnlockBucket ببيانات اعتماده. لا يمكن لكلمة المرور الرئيسية وحدها فك تشفير المجموعة. إلغاء صلاحية مسؤول واحد لا يؤثر على النسخة المغلفة لأي مسؤول آخر.
يتم إنشاء DEK للمجموعة في وقت CreateBucket ويُغلّف فورًا بواسطة HSMProvider يوفره المتصل. يقوم المزوّد بعمليات التغليف وفك التغليف — لا يتعامل Keeper مع DEK الخام بعد تسليمه للمزوّد. يستدعي UnlockDatabase تلقائيًا المزوّد لفك التغليف وتغذية الغلاف (Envelope) لجميع مجموعات HSM المسجلة. تدوير المفتاح الرئيسي لا يُعيد تشفير هذه المجموعات؛ DEK يتحكم فيه المزوّد.
تتوفر في pkg/hsm تطبيق SoftHSM مدمج يعتمد على مفتاح تغليف محمي بـ memguard للاختبار وبيئات CI. لا تستخدمه في الإنتاج.
مطابق لـ LevelHSM في سلوك إدارة المفاتيح، لكن HSMProvider يتم تنفيذه بواسطة pkg/remote.Provider — وهو محول HTTPS قابل للتكوين يفوض التغليف وفك التغليف إلى أي خدمة KMS عن بُعد عبر TLS. توجد تكوينات مسبقة لـ HashiCorp Vault Transit و AWS KMS و GCP Cloud KMS في pkg/remote. للاستخدام في الإنتاج، قم بتكوين TLSClientCert و TLSClientKey لتفعيل مصادقة TLS المتبادلة.
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 ليس سرًا؛ الغرض منه هو التفرد، وليس السرية.
يتم تشفير كل قيمة نصية واضحة باستخدام XChaCha20-Poly1305 باستخدام مفتاح تشفير البيانات للمجموعة (bucket DEK):``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
السجل المخزّن هو بنية `Secret` مشفّرة بـ msgpack تحتوي على النص المشفر، والبيانات الوصفية المشفّرة، وإصدار المخطط. المصادقة ضمنية: أي نص مشفر يتم فك تشفيره بمفتاح خاطئ ينتج عنه فشل في مصادقة AEAD قبل إرجاع أي نص واضح.
### 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)
يتم اشتقاق KEK باستخدام HKDF بدلاً من تمريرة Argon2 ثانية. المفتاح الرئيسي تم إنتاجه بالفعل بواسطة KDF عالي التكلفة؛ استدعاء ثانٍ لـ Argon2 من شأنه أن يضيف مئات المللي ثانية من زمن الانتظار لكل استدعاء UnlockBucket دون أي فائدة أمنية. تعمل HKDF-SHA256 في حوالي ميكروثانية واحدة.
دفاع متعمق: المهاجم الذي يخترق قاعدة البيانات فقط يحصل على DEK المغلف وملح HKDF لكنه لا يستطيع اشتقاق KEK بدون المفتاح الرئيسي. المهاجم الذي يخترق المفتاح الرئيسي فقط لا يستطيع فك تغليف أي DEK من نوع LevelAdminWrapped دون معرفة بيانات اعتماد المشرف أيضًا.
يتم تشفير البيانات الوصفية السرية (وقت الإنشاء، وقت التحديث، عدد مرات الوصول، الإصدار) بشكل منفصل عن النص المشفر:``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
بالنسبة للحاويات `LevelAdminWrapped` و `LevelHSM` و `LevelRemote`، هذا يعني أن البيانات الوصفية غير قابلة للوصول بدون بيانات اعتماد الحاوية، مما يمنع المهاجم الذي لديه حق الوصول للقراءة إلى ملف قاعدة البيانات من معرفة أنماط الوصول أو الطوابع الزمنية.
**ملاحظة حول القنوات الجانبية الزمنية:** يقوم XChaCha20-Poly1305 بمعالجة النص المشفر بالكامل قبل إرجاع خطأ في المصادقة. مسار فك التشفير الاحتياطي (مفتاح تشفير البيانات المستمد الجديد → مفتاح رئيسي قديم كمفتاح تشفير بيانات) يستغرق نفس الوقت الفعلي بغض النظر عن أي مفتاح ينجح. لا يوجد قناة جانبية زمنية تسرب حالة ترحيل السجل.
### تشفير البيانات الوصفية — السياسات، سجل الكتابة المسبقة (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 القابلة للتكوين المستخدمة للأسرار — اختيار المستخدم للتشفير (AES-256-GCM لـ FIPS، XChaCha20-Poly1305 افتراضيًا) يتم تطبيقه تلقائيًا.
تنسيق الإرسال لجميع كتل البيانات الوصفية المشفرة:``` nonce (cipher.NonceSize() bytes) || AEAD-ciphertext
### تجزئة مفتاح دلو السياسة