الأسرار في Kubernetes وCI
الأسرار في Kubernetes وCI
أسرار Kubernetes الأصلية مُشفَّرة بـ base64 فقط، وليست مُعمَّاة. أي مهندس لديه صلاحية kubectl get secret يمكنه قراءة كل كلمة مرور ومفتاح API وشهادة TLS في الـ namespace. في Google وMeta وNetflix، تُعامَل منظومة أسرار Kubernetes كـ ناقل توزيع لا كخزنة — مصدر الحقيقة الفعلي يقع في HashiCorp Vault أو AWS Secrets Manager أو GCP Secret Manager. الأداتان اللتان تجسّران هذه الهوة هما External Secrets Operator (ESO) وSecrets Store CSI Driver. لخطوط CI، الإجابة الحديثة على مشكلة بيانات الاعتماد هي مصادقة OIDC بلا مفاتيح. يغطي هذا الدرس الثلاثة، إضافة إلى أنماط الفشل التي تُسرِّب الأسرار إلى سجلات البناء وتفريغ البيئة.
External Secrets Operator (ESO)
يعمل ESO كمتحكم داخل المجموعة. تُنشئ CRD باسم ExternalSecret يُشير إلى إدخال في مخزن الأسرار الخارجي؛ يجلبه ESO بفاصل زمني قابل للضبط ويُجسّده كـ Secret Kubernetes اعتيادي. التطبيقات تقرأ الـ Secret الأصلي — لا تحتاج أي SDK أو sidecar. يبقى المخزن الخارجي مصدر الحقيقة الوحيد؛ ESO هو محرك مزامنة للقراءة فقط.
النموذج ثنائي الطبقات: SecretStore (أو ClusterSecretStore) يحمل إعدادات المزود وبيانات اعتماده، بينما يُعلن ExternalSecret أي مفاتيح تُزامَن. الفصل بينهما يعني أن فريق المنصة يُدير بيانات اعتماد المخزن بينما تُؤلّف فرق التطبيقات كائنات ExternalSecret خاصة بها.
creationPolicy: Owner مهم: عند حذف ExternalSecret، يُزيل ESO تلقائياً الـ Kubernetes Secret المُشتَق. بدون ذلك، تبقى الأسرار اليتيمة في الـ namespace إلى أجل غير مسمى، متراكمةً ببيانات اعتماد قديمة تُفشل عمليات التدقيق وتُربك المشغّلين. اضبط دائماً Owner في الإنتاج؛ استخدم Merge فقط حين تجمع عمداً عدة ExternalSecrets في Secret واحد.Secrets Store CSI Driver
بينما يُجسّد ESO Secret Kubernetes (الذي يستقر في etcd)، يأخذ CSI Driver نهجاً مختلفاً: يُثبّت السر مباشرةً في نظام ملفات الـ pod كملف، متجاوزاً etcd كلياً. لا يلمس السر مستوى التحكم في Kubernetes أثناء السكون. يُلبّي هذا متطلبات الامتثال الأكثر صرامة (PCI DSS Level 1، FedRAMP High) حيث حتى تخزين etcd المُعمَّى غير مقبول.
يتكون Driver من DaemonSet على كل عقدة وإضافات خاصة بالمزود. يستخدم مزود AWS الـ IRSA (IAM Roles for Service Accounts)؛ مزود Azure يستخدم Managed Identity؛ مزود Vault يستخدم نمط vault agent injector لكن بدون sidecar.
مصادقة OIDC بلا مفاتيح في CI
مشكلة أسرار CI التقليدية: خط الأنابيب يحتاج رمزاً لـ AWS أو Vault، لذا تُخزّنه في متغيرات GitHub/GitLab CI — الآن بيانات الاعتماد الثابتة هذه سر طويل الأمد يمكن سرقته من مخرجات السجل أو تفريغ البيئة أو runner مُخترَق. تُزيل مصادقة OIDC بلا مفاتيح بيانات الاعتماد الثابتة من خطوط CI كلياً.
الآلية: حين تعمل وظيفة GitHub Actions، يُصدر مزود OIDC الخاص بـ GitHub JWT قصير الأمد (مُحدَّد النطاق، موقَّع من GitHub) يُثبت أي مستودع وفرع وسير عمل شغَّل الوظيفة. يُهيَّأ AWS (أو Vault أو GCP أو Azure) للوثوق بمزود OIDC الخاص بـ GitHub ومبادلة الـ JWT ببيانات اعتماد سحابية صالحة لمدة الوظيفة — عادةً 15 دقيقة.
sub في سياسة ثقة IAM هي التحكم الأساسي في نطاق التأثير. repo:my-org/payments:ref:refs/heads/main تُقيّد الدور على دفعات فرع main فقط. طلب سحب من fork لا يستطيع افتراض هذا الدور. في المؤسسات الكبيرة، أنشئ دوراً منفصلاً لكل بيئة (staging مقابل production) مع شروط sub مختلفة — لا تسمح أبداً لـ pipeline التجهيز بافتراض دور نشر الإنتاج.تجنب تسريب متغيرات البيئة في CI وKubernetes
حتى حين تُسلَّم الأسرار بشكل صحيح، تتسرب عادةً عبر أخطاء تشغيلية. هذه هي أنماط الفشل الموثّقة في postmortems الحوادث الحقيقية:
- حقن السجل عبر
set -x: وضع تصحيح Bash يطبع كل أمر مع وسيطاته المُوسَّعة. سكريبت يُشغّلcurl -H "Authorization: Bearer $TOKEN" ...مع تفعيلset -xسيطبع الرمز حرفياً. لا تستخدمset -xفي سكريبتات CI التي تتعامل مع الأسرار؛ استخدمset -e(الفشل السريع) بدلاً منه. - تفريغ البيئة:
envوprintenvوkubectl exec -- envوصفحات تصحيح الأطر (Django DEBUG=True، Laravel APP_DEBUG=true) ستطبع كل متغير بيئة. عطّل نقاط النهاية التصحيحية في الإنتاج ولا تُشغّلenvفي خطوة CI السجل. - Docker build-time ARGs:
docker build --build-arg DB_PASSWORD=secretيُضمّن القيمة في تاريخ طبقات الصورة، قابلة للقراءة من قِبَل أي شخص لديهdocker history myimage. استخدم multi-stage builds ومرّر الأسرار وقت التشغيل؛ لاحتياجات وقت البناء، استخدم Docker BuildKit secret mounts. - Kubernetes Secret في YAML مُودَع في Git: سر مُشفَّر بـ base64 في YAML مُودَع فعلياً نص عادي. استخدم SOPS أو Sealed Secrets للتعمية قبل الإيداع، أو الأفضل، لا تُودع قيم الأسرار إطلاقاً — خزّنها في المخزن الخارجي وأشر إليها عبر ESO.
- مخرجات describe وحصص الموارد:
kubectl describe podيُظهر إدخالاتenvبما فيها القيم منvalueFrom.secretKeyRef— تلك القيم تظهر مُخفَّاة في إصدارات Kubernetes الحديثة، لكن المجموعات الأقدم تكشفها. دقّق من لديه صلاحية RBAC لـdescribeعلى الـ namespace.
GITHUB_ENV: كتابة سر إلى $GITHUB_ENV يجعله متاحاً كمتغير بيئة للخطوات اللاحقة، لكنه يظهر أيضاً في مخرجات Set up job في بعض إصدارات runner. للأسرار التي يجب تمريرها بين الخطوات، يُفضَّل كتابتها إلى ملف مؤقت بصلاحيات 600، أو استخدام متغيرات إخراج GitHub Actions مع الإخفاء. لا تكتب أبداً قيمة سر خام إلى خطوة سجل أو رفع artifact.ESO مقابل CSI Driver: متى تستخدم أيهما
كلا الأداتين تحلان المشكلة ذاتها لكنهما تناسبان متطلبات امتثال مختلفة. ESO هو الافتراضي الصحيح لغالبية أحمال العمل: أبسط تشغيلاً، يتكامل مع أي أداة Kubernetes تقرأ Secrets الأصلية، ويدعم الدوران بدون إعادة تشغيل الـ pods. استخدم CSI Driver حين يُلزم الامتثال بأن الأسرار لا يجب أن تلمس etcd إطلاقاً — بيئات PCI DSS، أحمال العمل الحكومية، أو السيناريوهات التي لا تتحكم فيها بالتعمية في المستوى التحكمي.
في الممارسة، تُشغّل المنصات الكبيرة كليهما: ESO لأسرار التطبيقات (بيانات اعتماد قواعد البيانات، مفاتيح API) وCSI Driver لمفاتيح TLS الخاصة والمواد المدعومة بـ HSM حيث متطلب الحدود التشفيرية أكثر صرامة.
الخلاصة
أسرار Kubernetes الأصلية هي آلية توزيع لا حدود ثقة. External Secrets Operator يُزامن الأسرار من Vault أو مخازن الأسرار السحابية إلى Secrets الأصلية عبر نموذج سحب، محتفظاً بالمخزن الخارجي كمصدر للحقيقة. CSI Driver يُسلّم الأسرار كتثبيتات tmpfs، متجاوزاً etcd للبيئات عالية الامتثال. مصادقة OIDC بلا مفاتيح تُزيل بيانات الاعتماد الثابتة من خطوط CI كلياً، مستعيضةً عنها برموز قصيرة الأمد ومُحدَّدة النطاق مرتبطة بمستودع وفرع محددين. منع تسريب متغيرات البيئة هو انضباط تشغيلي — الأدوات ضرورية لكن غير كافية بدون نظافة السجلات وانضباط طبقات Docker وتحديد نطاق RBAC على صلاحية describe.