أحمال عمل Kubernetes وإعدادها

الأسرار (Secrets)

18 دقيقة الدرس 2 من 32

الأسرار (Secrets)

كل بيئة إنتاجية تحتاج في نهاية المطاف إلى بيانات اعتماد: كلمة مرور قاعدة بيانات، أو مفتاح API، أو شهادة TLS، أو سر عميل OAuth. أسوأ خطأ يمكن أن يرتكبه المهندس هو تضمين تلك البيانات داخل صورة الحاوية أو رفعها إلى مستودع Git. تمثّل Secrets في Kubernetes الإجابة الأصلية للمنصة على هذه المشكلة — غير أنها تأتي مع دقائق مهمة تتعلق بالترميز والتشفير وضبط الصلاحيات يجب على كل محترف فهمها قبل الانتقال إلى الإنتاج.

ما هو السر — وما ليس كذلك

السر هو كائن Kubernetes مُخزَّن في etcd — مخزن القيم-المفاتيح الخاص بالمجموعة — جنبًا إلى جنب مع كل مورد آخر. بشكل افتراضي، قيم السر مُرمَّزة بـ base64 وليست مُشفَّرة. base64 ليس حاجزًا أمنيًا؛ إنه مجرد ترميز نقل لضمان التخزين الآمن للبيانات الثنائية. أي شخص لديه صلاحية kubectl get secret على المساحة يمكنه فك الترميز بأمر واحد. هذا أهم ما يجب استيعابه عن Kubernetes Secrets: كائن الـ API بحد ذاته يوفر العزل، لا السرية.

السرية الحقيقية تتطلب التشفير في حالة السكون (تشفير بيانات etcd على القرص عبر مورد EncryptionConfiguration) مقرونًا بـ RBAC صارم يحصر صلاحيات get/list/watch على كائنات Secret للأحمال العمل والأشخاص المخوّلين فحسب.

أنواع Secrets

يتعرّف Kubernetes على عدة أنواع مدمجة، يتم التحقق من صحة كل منها والتعامل معه بشكل مختلف:

  • Opaque — النوع الافتراضي. حقيبة مفاتيح وقيم اعتباطية. استخدمه لكلمات المرور ورموز API وسلاسل الاتصال وأي بيانات اعتماد مخصصة.
  • kubernetes.io/dockerconfigjson — بيانات اعتماد سحب الصور من السجلات الخاصة. يُشار إليها عبر imagePullSecrets في مواصفة Pod. يُلزم الـ API بوجود بنية .dockerconfigjson صحيحة.
  • kubernetes.io/tls — زوج شهادة TLS ومفتاحها. يجب تسمية المفاتيح tls.crt وtls.key. تستخدمها وحدات تحكم Ingress وcert-manager.
  • kubernetes.io/service-account-token — تُنشئها لوحة التحكم تلقائيًا لكل ServiceAccount. تُثبَّت تلقائيًا داخل Pods ما لم يُضبط automountServiceAccountToken: false.
  • kubernetes.io/basic-auth وkubernetes.io/ssh-auth — أنواع متخصصة بأسماء مفاتيح مُلزَمة لبيانات المصادقة الأساسية ومفاتيح SSH الخاصة.
Kubernetes Secret Types and Consumers Secret (etcd — base64) EncryptionConfig → AES Pod — env var secretKeyRef Pod — volume tmpfs mount Ingress TLS tls.crt / tls.key imagePullSecrets Private registry External Secrets AWS SM / Vault cert-manager Auto-renew TLS RBAC — get/list/watch limit to namespace SA only
أنواع Secret ومستهلكوها. يمثّل السهم المتقطع مشغّل External Secrets وهو يُزامن من مخزن خارجي إلى Secret أصلي في Kubernetes.

إنشاء Secrets

الطريقة الأأمن لإنشاء Secret هي إنشاؤه بشكل إلزامي من ملف، حتى لا يظهر النص الصريح في سجل الأوامر. تُقبل ملفات YAML التي تحتوي على قيم base64 فقط عند تخزينها في أداة لإدارة الأسرار (Sealed Secrets أو Vault أو مشغّل external secrets)، وليس أبدًا كـ YAML عادي في Git.

# --- من قيم حرفية (لا تستخدم --from-literal في الإنتاج؛ يُسجَّل في سجل الأوامر) --- kubectl create secret generic db-creds \ --from-literal=password=supersecret \ --namespace=payments # --- الأفضل: من ملفات، حتى لا تظهر القيمة في الـ shell --- echo -n 'supersecret' > /tmp/db-password.txt # -n تحذف السطر الجديد kubectl create secret generic db-creds \ --from-file=password=/tmp/db-password.txt \ --namespace=payments shred -u /tmp/db-password.txt # اتلاف الملف المؤقت # --- إنشاء سر TLS من زوج شهادة/مفتاح موجودَين --- kubectl create secret tls api-tls \ --cert=server.crt \ --key=server.key \ --namespace=payments # --- إنشاء سر سحب صور لسجل خاص --- kubectl create secret docker-registry ghcr-creds \ --docker-server=ghcr.io \ --docker-username=myorg \ --docker-password=$GHCR_TOKEN \ --namespace=payments # --- فحص السر (سيُعرض بـ base64؛ فك التشفير يدويًا) --- kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 --decode
سجل الأوامر مخزن بيانات اعتماد. أي قيمة سرية تُمرَّر عبر --from-literal تُكتب في ملف سجل الـ shell وقد تظهر في سجلات التدقيق أو سجلات CI أو جلسات العمل المشترك. اكتب دائمًا بيانات الاعتماد إلى ملف أولًا (echo -n لتجنب السطر الجديد الذي يكسر جولة base64)، ثم أشِر إلى الملف، ثم احذفه. في سكريبتات الإنتاج الطارئة، اضبط HISTFILE=/dev/null للجلسة.

تثبيت Secrets داخل Pods

هناك طريقتان لإظهار Secret داخل حاوية: كـ متغيرات بيئة أو كـ ملفات مُثبَّتة عبر volume. التثبيت عبر volume هو الأفضل دائمًا في الإنتاج لأن متغيرات البيئة تظهر في قوائم العمليات (/proc/<pid>/environ)، وتُسجَّل بواسطة كثير من الأُطر عند الإقلاع، وتُورَث بواسطة العمليات الفرعية. يُثبِّت Kubernetes وحدات تخزين Secret على نظام ملفات tmpfs (في الذاكرة، لا يُكتب أبدًا على القرص)، وهو تحسين أمني ملموس مقارنةً بمتغيرات البيئة.

# --- مانيفست: كلا نمطي التثبيت جنبًا إلى جنب --- apiVersion: v1 kind: Pod metadata: name: app namespace: payments spec: serviceAccountName: payments-app # SA محدود، ليس الافتراضي automountServiceAccountToken: false # إلغاء ما لم يحتج التطبيق لـ API volumes: - name: db-secret-vol secret: secretName: db-creds defaultMode: 0400 # للقراءة فقط، المالك وحده items: - key: password path: db-password # مُثبَّت في /etc/secrets/db-password containers: - name: app image: ghcr.io/myorg/payments:v3.1.0 # النمط 1: متغير بيئة (مقبول للإعداد غير الحساس؛ تجنّبه لكلمات المرور) env: - name: DB_HOST value: "postgres.payments.svc.cluster.local" - name: DB_PASSWORD # سر — افضّل volume لهذا valueFrom: secretKeyRef: name: db-creds key: password # النمط 2: تثبيت volume (الأفضل لبيانات الاعتماد) volumeMounts: - name: db-secret-vol mountPath: /etc/secrets readOnly: true # يقرأ التطبيق /etc/secrets/db-password عند الإقلاع بدلًا من متغير بيئة
تدوير Secrets التلقائي: عند تحديث Secret، يُوزِّع Kubernetes القيمة الجديدة على وحدات تخزين الـ volume خلال فترة مزامنة kubelet (افتراضيًا ~60 ثانية). متغيرات البيئة لا تُحدَّث — يجب إعادة تشغيل Pod. لتدوير بيانات الاعتماد بدون توقف، استخدم دائمًا وحدات تخزين volume واكتب تطبيقك ليُعيد قراءة الملف عند استقبال إشارة أو بعد مؤقت.

التشفير في حالة السكون

بشكل افتراضي، يُخزِّن etcd بيانات Secret بنص واضح (يُفكّ ترميز base64 قبل الحفظ). لتفعيل التشفير في حالة السكون، طبِّق EncryptionConfiguration على API server. تدعم خدمات Kubernetes المُدارة (EKS وGKE وAKS) هذا عبر إعداد مجموعة بسطر واحد (مثلًا --secrets-encryption-key-arn في EKS مع مفتاح AWS KMS).

في المجموعات ذاتية الإدارة، يُشار إلى مانيفست EncryptionConfiguration عبر خيار API server رقم --encryption-provider-config. يستخدم مزوّدا aescbc أو aesgcm مفتاحًا مُدارًا محليًا؛ أما مزوّد kms (الموصى به بشدة) فيُفوِّض الأمر إلى KMS خارجي حتى لا يقطن المفتاح على عقدة المجموعة أبدًا.

تحقّق من أن التشفير فعّال فعلًا — لا يكفي فحص إعداد المجموعة. بعد التفعيل، شغِّل: kubectl get secret db-creds -o json | etcdctl get ... وتأكد أن قيمة etcd الخام تبدأ بـ k8s:enc:aescbc (أو المزوّد الذي اخترته). كثير من الفرق تُفعِّل الإعداد لكنها تنسى إعادة تشفير Secrets الموجودة عبر kubectl get secrets --all-namespaces -o json | kubectl replace -f -.

Secrets على مستوى الإنتاج: Sealed Secrets وExternal Secrets Operator

لا يمكن الالتزام بأمان بمانيفستات Kubernetes Secret العادية في Git لأنها تحتوي على قيم base64 يمكن لأي مطوّر فك ترميزها. يحلّ مشروعان مفتوحا المصدر هذه المشكلة على نطاق واسع:

  • Sealed Secrets (Bitnami) — مُتحكِّم يحتفظ بمفتاح RSA خاص على المجموعة. تُشفِّر مانيفست Secret إلى CRD باسم SealedSecret باستخدام المفتاح العام المقابل عبر أداة سطر الأوامر kubeseal. ملف YAML المختوم آمن للالتزام به في Git — فقط مُتحكِّم المجموعة يمكنه فك تشفيره. يُوفِّق المُتحكِّم كائنات SealedSecret إلى Kubernetes Secrets حقيقية داخل المجموعة. يناسب هذا نموذج GitOps تمامًا: كل شيء في Git، لا شيء بنص صريح.
  • External Secrets Operator (ESO) — مُتحكِّم يُزامن الأسرار من مخزن خارجي (AWS Secrets Manager وHashiCorp Vault وGCP Secret Manager وAzure Key Vault وغيرها) إلى Kubernetes Secrets. تُعرِّف CRD باسم ExternalSecret يُحدِّد مسار المخزن الخارجي واسم Secret المستهدف. يتولى ESO التدوير — عند تحديث السر الخارجي، يُعيد ESO المزامنة خلال refreshInterval المُهيَّأ. هذا هو النمط المفضَّل على نطاق كبير لأنه يحتفظ بمصدر الحقيقة الوحيد خارج المجموعة، ويتيح المشاركة عبر المجموعات، ويتكامل مع سياسات إدارة الأسرار الحالية.
# --- Sealed Secrets: ختم مانيفست Secret قبل الالتزام به في Git --- # 1. جلب المفتاح العام للمجموعة (مرة واحدة، خزِّنه في المستودع) kubeseal --fetch-cert \ --controller-namespace=sealed-secrets \ --controller-name=sealed-secrets > pub-cert.pem # 2. ختم مانيفست Secret موجود kubectl create secret generic db-creds \ --from-literal=password=supersecret \ --dry-run=client -o yaml \ | kubeseal --cert=pub-cert.pem --format=yaml > sealed-db-creds.yaml # 3. الالتزام بـ sealed-db-creds.yaml في Git — إنه آمن (مُشفَّر بشكل غير متماثل) git add sealed-db-creds.yaml && git commit -m "chore: add sealed DB credentials" # --- External Secrets Operator: المزامنة من AWS Secrets Manager --- # ExternalSecret CRD (الزم به في Git — لا بيانات حساسة) apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-creds namespace: payments spec: refreshInterval: 1h secretStoreRef: name: aws-secretsmanager # ClusterSecretStore يشير إلى AWS SM kind: ClusterSecretStore target: name: db-creds # الـ Kubernetes Secret الذي سيُنشئه/يُحدِّثه ESO creationPolicy: Owner data: - secretKey: password # المفتاح في الـ K8s Secret الناتج remoteRef: key: payments/db # اسم السر في AWS Secrets Manager property: password # خاصية JSON داخل سر SM
الاختيار بين الاثنين: استخدم Sealed Secrets عندما تعيش دورة حياة الأسرار بالكامل داخل مجموعة Kubernetes واحدة وتستخدم بالفعل GitOps (ArgoCD/Flux). استخدم External Secrets Operator عندما يكون لديك مخزن أسرار موجود (Vault أو AWS SM)، أو تحتاج إلى مشاركة أسرار عبر مجموعات، أو تحتاج إلى سياسات تدوير دقيقة تديرها فرقة أمن خارج Kubernetes. معظم المؤسسات الناضجة تستخدم ESO.

RBAC للـ Secrets — مبدأ أقل الصلاحيات

لأن Secrets هي كائنات API من الدرجة الأولى، يتحكم RBAC القياسي في Kubernetes بمن يمكنه قراءتها. الممارسة الأمنية الأكثر تأثيرًا هي ضمان عدم امتلاك أي تطبيق أو إنسان صلاحية list أو watch على Secrets في المساحات التي لا يمتلكها. صلاحية list على Secrets تُعيد جميع القيم — وهي معادلة لاختراق بيانات. حدِّد نطاق كل Role لـ ServiceAccount بـ get على أسماء Secret المحددة التي يحتاجها التطبيق فحسب، لا بحرف بدل.

دقِّق في وصول Secret بانتظام: kubectl auth can-i get secrets --as=system:serviceaccount:payments:payments-app -n payments وقارن مع سجل تدقيق API server بحثًا عن استدعاءات list/watch غير متوقعة على مورد secrets.