GitOps مع ArgoCD وFlux

الأسرار في GitOps

18 دقيقة الدرس 8 من 30

الأسرار في GitOps

يتطلب GitOps أن تعيش كل قطعة من الحالة المطلوبة في مستودع Git — لكن Git ليس مخزن أسرار. كتابة كلمة مرور قاعدة بيانات، أو مفتاح API، أو مفتاح TLS الخاص في نص عادي داخل مستودع هو واحد من أكثر الأخطاء الأمنية شيوعاً وعواقبها وخيمة في البنية التحتية الحديثة. حتى في مستودع خاص، نطاق الضرر الناتج عن تعرض غير مقصود يشمل كل مطور لديه صلاحية الاستنساخ، كل عداء CI، كل نسخة fork، وكل إدخال في reflog. على نطاق شركات التقنية الكبرى، يلمس آلاف المهندسين نفس المستودعات — commit واحد بسر عادي هو مسؤولية دائمة.

هذا الدرس يغطي الأنماط الثلاثة السائدة المستخدمة في بيئات GitOps الإنتاجية لحل هذه المشكلة: Sealed Secrets، وSOPS، وExternal Secrets Operator. كل منها يحل جزءاً مختلفاً من الفضاء المشكلة. بنهاية الدرس، ستعرف أي أداة تناسب أي سياق وكيف تُنفّذ كل واحدة بشكل صحيح.

التوتر الجوهري: GitOps يقول "ضع كل شيء في Git." الأمن يقول "لا تضع الأسرار أبداً في Git." الحل ليس كسر أحد هذين القاعدتين — بل ضمان أن ما يدخل Git هو تمثيل مشفّر أو مرجع للسر، وليس السر نفسه. الكلاستر، وفقط الكلاستر، يمكنه فك التشفير أو استرداد القيمة الحقيقية.

لماذا Kubernetes Secrets وحدها لا تكفي

كائن Kubernetes Secret مُرمَّز بـ base64، لا مُشفَّر. أي شخص يمكنه قراءة قاعدة بيانات etcd أو تشغيل kubectl get secret my-db-creds -o yaml مع صلاحيات RBAC المناسبة يحصل على القيمة الخام. إذا ألزمت مانيفست Secret في Git، فأنت قد ألزمت السر بنص عادي مع تمويه خفيف. هذه هي نقطة البداية التي وُجدت كل أداة إدارة الأسرار في GitOps لإصلاحها.

الخيار الأول: Sealed Secrets (Bitnami)

Sealed Secrets هو كنترولر Kubernetes وأداة CLI من Bitnami. النموذج هو تشفير غير متماثل مُدمج في الكلاستر نفسه. يحتفظ الكنترولر بمفتاح خاص داخل الكلاستر؛ تستخدم المفتاح العام المقابل (يُجلب عبر CLI) لتشفير Kubernetes Secret عادي إلى مورد مخصص SealedSecret. من الآمن ألزام SealedSecret في Git — فقط الكنترولر الذي يحتفظ بالمفتاح الخاص يمكنه فك تشفيره.

تثبيت الكنترولر عبر Helm:

# أضف مستودع Sealed Secrets Helm وثبّت الكنترولر helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets helm repo update helm install sealed-secrets sealed-secrets/sealed-secrets \ --namespace kube-system \ --set fullnameOverride=sealed-secrets-controller # ثبّت kubeseal CLI (مثال macOS؛ Linux: حمّله من إصدارات GitHub) brew install kubeseal

بمجرد تشغيل الكنترولر، اختم سراً باستخدام kubeseal:

# الخطوة 1 – أنشئ مانيفست Secret عادي (غير مشفّر) في ملف مؤقت kubectl create secret generic db-credentials \ --from-literal=username=appuser \ --from-literal=password='S3cur3P@ssw0rd!' \ --namespace=production \ --dry-run=client \ -o yaml > /tmp/db-credentials.yaml # الخطوة 2 – اختمه (يجلب المفتاح العام من الكنترولر تلقائياً) kubeseal \ --controller-name=sealed-secrets-controller \ --controller-namespace=kube-system \ --format yaml \ < /tmp/db-credentials.yaml \ > gitops-config/secrets/db-credentials-sealed.yaml # الملف المختوم آمن للإلزام. احذف الملف المؤقت بنص عادي. rm /tmp/db-credentials.yaml # الخطوة 3 – ألزم وادفع git add gitops-config/secrets/db-credentials-sealed.yaml git commit -m "feat: add sealed db-credentials for production" git push

مانيفست SealedSecret الناتج يبدو هكذا (النص المشفّر مختصر):

apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: db-credentials namespace: production spec: encryptedData: password: AgBy3i4OJSWK+PiTySYZZA24... # مشفّر بتشفير غير متماثل username: AgCH4T2L8tuxMGfD3... template: metadata: name: db-credentials namespace: production type: Opaque

عندما يطبّق ArgoCD أو Flux هذا المانيفست، يعترضه كنترولر Sealed Secrets، يفك تشفير القيم باستخدام مفتاحه الخاص، وينشئ Kubernetes Secret قياسياً في نفس الـ namespace. تثبّت Pods السر بشكل طبيعي — لا تعلم أبداً عن طبقة الختم.

حدّد نطاق الأسرار المختومة بشكل صحيح. بشكل افتراضي، ينتج kubeseal سراً مختوماً محدد النطاق بـ namespace يمكن فك تشفيره فقط في الـ namespace المُعلن. استخدم --scope cluster-wide فقط إذا كنت حقاً تحتاج إلى سر يمكن الوصول إليه في namespace متعددة — يُمثّل نطاق انفجار أوسع إذا تسرب الملف المختوم.
تدوير مفاتيح الكنترولر هو مسؤولية إنتاجية. ينشئ كنترولر Sealed Secrets مفتاح ختم جديداً كل 30 يوماً بشكل افتراضي. تُحتفظ المفاتيح القديمة لفك التشفير لكن الأسرار الجديدة تُختم بأحدث مفتاح. إذا حذفت الكنترولر دون نسخ احتياطي لمفاتيحه (المخزنة كـ Secrets في kube-system)، تفقد بشكل دائم القدرة على فك تشفير SealedSecrets الموجودة. اعمل نسخة احتياطية من مفتاح السر: kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > sealed-secrets-master-key-backup.yaml — وخزّن هذه النسخة الاحتياطية في قبو آمن، لا في Git.

الخيار الثاني: SOPS (Mozilla) — ملفات مشفّرة في Git

SOPS (Secrets OPerationS) هي أداة تشفير على مستوى الملف من Mozilla. بدلاً من كنترولر خاص بـ Kubernetes، يعمل SOPS على مستوى نظام الملفات: يُشفّر القيم في ملف YAML أو JSON أو ENV بينما يترك المفاتيح قابلة للقراءة. يمكن أن يكون الخلفية التشفيرية AWS KMS أو GCP KMS أو Azure Key Vault أو HashiCorp Vault أو age (أداة تشفير حديثة وبسيطة). في الممارسة، age هو الافتراضي للفرق بلا KMS سحابي، بينما يُفضَّل AWS/GCP KMS في بيئات السحابة لأن الوصول للمفاتيح مرتبط بـ IAM — لا مواد مفتاح طويلة الأمد للإدارة.

تشفير Kubernetes Secret باستخدام SOPS + age:

# ثبّت age و sops brew install age sops # macOS # apt install age sops # Debian/Ubuntu # أنشئ زوج مفاتيح age (افعل هذا مرة واحدة لكل هوية مشغّل/CI) age-keygen -o age-key.txt # المفتاح العام: age1qldzds... # المفتاح الخاص مخزّن في age-key.txt -- لا تُلزم هذا الملف أبداً # أضف المفتاح العام age إلى .sops.yaml في جذر المستودع -- هذا يُلزَم cat > .sops.yaml << 'EOF' creation_rules: - path_regex: secrets/.*\.yaml$ age: age1qldzds6xnekn3yjcxgkuq6y8w6g5hkzxm... # استبدل بمفتاحك العام الحقيقي EOF # أنشئ ملف السر بنص عادي مؤقتاً cat > /tmp/db-secret.yaml << 'EOF' apiVersion: v1 kind: Secret metadata: name: db-credentials namespace: production type: Opaque stringData: username: appuser password: S3cur3P@ssw0rd! EOF # شفّر باستخدام sops -- يُخرج النسخة المشفّرة SOPS_AGE_KEY_FILE=age-key.txt sops --encrypt /tmp/db-secret.yaml \ > gitops-config/secrets/db-secret.yaml # تحقق من قراءة المفاتيح (القيم مشفّرة): # apiVersion: v1 # kind: Secret # ... # stringData: # username: ENC[AES256_GCM,data:4wd...,type:str] # password: ENC[AES256_GCM,data:rB2...,type:str] # ألزم الملف المشفّر -- آمن للدفع git add .sops.yaml gitops-config/secrets/db-secret.yaml git commit -m "feat: add SOPS-encrypted db credentials" git push # على جانب الكلاستر (Flux مع دعم فك تشفير SOPS): # أنشئ Kubernetes Secret يحتوي على المفتاح الخاص age kubectl create secret generic sops-age \ --namespace=flux-system \ --from-file=age.agekey=age-key.txt # أشر إليه في مورد Flux Kustomization: # decryption: # provider: sops # secretRef: # name: sops-age
Three GitOps secrets patterns side by side Sealed Secrets kubeseal encrypts with cluster pubkey SealedSecret in Git (safe to commit) Controller decrypts using private key k8s Secret created Pod mounts it Key lives in cluster No external vault needed SOPS + age / KMS sops --encrypt with age / KMS key Encrypted YAML in Git (values encrypted) Flux/ArgoCD decrypts via sops provider k8s Secret created Pod mounts it Flexible backends Audit trail via KMS External Secrets Operator ExternalSecret in Git references vault path ESO fetches from AWS SM / Vault / GCP SM k8s Secret synthesized from live vault value k8s Secret created Pod mounts it Secret never in Git at all Best for cloud-native orgs
ثلاثة أساليب لإدارة الأسرار في GitOps — Sealed Secrets (مفتاح داخل الكلاستر)، SOPS (قيم مشفّرة في Git)، و External Secrets Operator (مرجع فقط في Git).

الخيار الثالث: External Secrets Operator (ESO)

يتبنى ESO نهجاً فلسفياً مختلفاً: الأسرار لا تُخزَّن أبداً في Git، حتى مشفّرة. بدلاً من ذلك، تُلزم مورداً مخصصاً خفيف الوزن من نوع ExternalSecret يصف أين تجد السر (مسار AWS Secrets Manager، مسار HashiCorp Vault، اسم GCP Secret Manager، إلخ) وكيف تُعيّنه إلى Kubernetes Secret. يقوم كنترولر ESO، الذي يعمل في الكلاستر، باستطلاع القبو الخارجي وتوليف Kubernetes Secret تلقائياً. عندما يتدوّر السر في القبو، يلتقط ESO القيمة الجديدة تلقائياً خلال فترة التحديث المُهيّأة.

# ثبّت ESO عبر Helm helm repo add external-secrets https://charts.external-secrets.io helm repo update helm install external-secrets external-secrets/external-secrets \ --namespace external-secrets \ --create-namespace \ --set installCRDs=true # أنشئ SecretStore يشير إلى AWS Secrets Manager. # يستخدم الكنترولر دور IAM مرتبطاً بـ ServiceAccount (IRSA / EKS Pod Identity). cat > gitops-config/secrets/cluster-secretstore.yaml << 'EOF' apiVersion: external-secrets.io/v1beta1 kind: ClusterSecretStore metadata: name: aws-secretsmanager spec: provider: aws: service: SecretsManager region: us-east-1 auth: jwt: serviceAccountRef: name: external-secrets-sa namespace: external-secrets EOF # أنشئ ExternalSecret يعيّن مسار Secrets Manager إلى k8s Secret cat > gitops-config/secrets/db-external-secret.yaml << 'EOF' apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-credentials namespace: production spec: refreshInterval: 1h # كم مرة يستطلع ESO القبو secretStoreRef: name: aws-secretsmanager kind: ClusterSecretStore target: name: db-credentials # اسم k8s Secret المراد إنشاؤه creationPolicy: Owner data: - secretKey: username # مفتاح في k8s Secret remoteRef: key: prod/db/credentials # مسار في Secrets Manager property: username # حقل JSON داخل السر - secretKey: password remoteRef: key: prod/db/credentials property: password EOF # كلا الملفين YAML عادي بلا قيم سرية -- آمن للإلزام git add gitops-config/secrets/ git commit -m "feat: add ExternalSecret for db credentials (ESO)" git push

اختيار الأداة المناسبة

على نطاق شركات التقنية الكبرى، الأدوات الثلاث ليست متنافية — تستخدم كثير من المؤسسات ESO للأسرار المُدارة سحابياً (AWS SM، GCP SM، Vault) وSOP أو Sealed Secrets لأسرار التشغيل الأساسي التي يجب أن تكون موجودة قبل الوصول إلى القبو:

  • Sealed Secrets — الأفضل للكلاسترات المُستضافة ذاتياً بلا KMS سحابي، للفرق الصغيرة، ودورات حياة الأسرار البسيطة. عبء تشغيلي منخفض. الضعف: الأسرار مقيّدة بالكلاستر الذي يحتفظ بمفتاح الختم؛ فقدان المفتاح كارثي.
  • SOPS — الأفضل عندما تريد أسراراً مشفّرة مخزّنة في Git مع تاريخ كامل لتغييرات الأسرار، ولديك KMS (AWS KMS، age) تثق به. يعمل مع ArgoCD (عبر إضافة Helm Secrets أو حاوية init للفك) وFlux (دعم SOPS أصلي). أوضح مسار تدقيق — كل تغيير سر هو git diff (للنص المشفّر).
  • External Secrets Operator — الأفضل للمؤسسات cloud-native التي لديها مدير أسرار موجود. تتدور الأسرار تلقائياً، يُحكم الوصول بسياسة IAM (لا صلاحيات الملفات)، ونطاق الانفجار عند اختراق مستودع Git يساوي صفراً — المستودع يحتوي فقط مراجع، لا قيم مشفّرة. العبء هو أنك تحتاج مدير أسرار يعمل ومُهيّأ بشكل صحيح.
النمط الإنتاجي في المؤسسات الكبيرة: استخدم ESO كنمط أسرار أساسي لأسرار التطبيقات (بيانات اعتماد قواعد البيانات، مفاتيح API، شهادات TLS). استخدم SOPS لتشفير بيانات اعتماد التشغيل الأساسي لكنترولر ESO نفسه (مفتاح IAM أو رمز Vault الذي يحتاجه للمصادقة على مدير الأسرار عند الإقلاع الأول) — تلك لا يمكن جلبها من القبو لأن اتصال القبو لم يُنشأ بعد. هذا النهج ذو الطبقتين يمنحك تدويراً تلقائياً، لا نص عادي في Git، ومسار تشغيل أساسي مُدار بالكامل عبر GitOps.
دقّق تاريخ Git قبل اعتماد أي من هذه الأدوات. إذا أُلزم سر بنص عادي في أي وقت — حتى في commit أُعيد لاحقاً — فهو لا يزال في reflog وفي أي نسخة مستنسخة. شغّل أداة مثل truffleHog أو git-secrets على كامل تاريخك. إذا وجدت واحداً، دوّر السر فوراً، ثم استخدم BFG Repo Cleaner أو git filter-repo لتطهير التاريخ وإجبار الدفع. تعامل مع السر كمخترق حتى تؤكد التدوير.

الدرس التالي ينتقل إلى ترقية البيئة — كيف تتدفق التغييرات من التطوير عبر الاختبار إلى الإنتاج في pipeline GitOps، بما في ذلك تحديثات عناوين الصور التلقائية وبوابات الترقية.