DevSecOps وأمن سلسلة التوريد

التوقيع والتحقق

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

التوقيع والتحقق

تستغل كل هجمات سلسلة التوريد الحديثة نفس ثغرة الثقة: المستهلك — سواء كان كلاسترًا في Kubernetes أو عداء CI أو محطة عمل مطور — يجلب artifact لا يستطيع إثبات أنها قادمت من المصدر الذي تدّعيه. ربما جرى استبدالها أثناء النقل أو على مستوى السجل أو داخل منظومة البناء ذاتها. يُغلق توقيع الـ Artifacts هذه الثغرة بربط توقيع تشفيري بالـ artifact وقت البناء والتحقق من هذا التوقيع قبل أي استهلاك. يغطي هذا الدرس أحدث الأدوات مفتوحة المصدر — Sigstore / Cosign — وكيفية تطبيق التحقق على طبقة الـ Admission في Kubernetes حتى لا تعمل أي صور غير موقّعة أو غير قابلة للتحقق في بيئة الإنتاج.

لماذا تفشل إدارة المفاتيح التقليدية على نطاق واسع

قبل Sigstore، كان توقيع الـ artifacts يعني إدارة مفاتيح GPG أو OpenSSL طويلة الأمد. على النطاق الكبير للشركات التقنية الكبرى، يُفرز هذا ثلاث مشكلات متراكمة. أولاً، توزيع المفاتيح: يجب أن يتلقى كل مستهلك المفتاح العام ويثق به عبر قناة خارجية — وهو إشكال لا حل نظيف له. ثانياً، تدوير المفاتيح: يُبطل المفتاح المُخترَق جميع التوقيعات السابقة، لكن إلغاء المفاتيح وإعادة توزيعها عبر مئات الفرق مُجهِد تشغيلياً. ثالثاً، الإسناد غير واضح: يُثبت التوقيع امتلاك المفتاح الخاص، لا هوية الإنسان أو مهمة CI التي شغّلت البناء فعلاً. إذا تسرّب المفتاح، لا توجد طريقة للتمييز بين التوقيعات الشرعية والخبيثة.

الفكرة الجوهرية: تستبدل Sigstore المفاتيح طويلة الأمد بأزواج مفاتيح مؤقتة مرتبطة برموز هوية OIDC. لا يوجد المفتاح الخاص إلا خلال عملية التوقيع ثم يُهمَل فوراً. ما يبقى هو شهادة يُصدرها Fulcio (الجهة المانحة للشهادات في Sigstore) تربط المفتاح العام المؤقت بموضوع OIDC مُتحقَّق منه — مثل رابط workflow في GitHub Actions. يُسجَّل التوقيع والشهادة في Rekor، وهو سجل شفافية غير قابل للتغيير، فيمكن لأي مُتحقِّق التدقيق في هوية من وقّع ماذا ومتى.

معمارية Sigstore: Fulcio وRekor وCosign

Sigstore signing and verification flow CI/CD Build (GitHub Actions) OIDC Provider GitHub / Google Fulcio CA Issues short-lived cert Rekor Transparency Log OCI Registry Image + Signature Admission Controller (Policy) 1. OIDC token 2. Token 3. Cert 4. Push img+sig 5. Record entry 6. Fetch sig 7. Verify log
تدفق التوقيع في Sigstore: يتبادل CI رمز OIDC مقابل شهادة Fulcio قصيرة الأمد، يوقّع الصورة، يُسجّل الإدخال في Rekor، ثم يتحقق منه وحدة التحكم في الـ Admission قبل تشغيل أي حمل عمل.

توقيع صورة الحاوية باستخدام Cosign (الوضع بلا مفاتيح)

في GitHub Actions، يعمل الـ workflow برمز OIDC افتراضياً. تكتشف Cosign ذلك وتُنفّذ دورة Fulcio/Rekor بشكل شفاف. لا تحتاج إلى أي إدارة للمفاتيح:

# .github/workflows/build-sign.yml (خطوة التوقيع ذات الصلة) - name: Install Cosign uses: sigstore/cosign-installer@v3 - name: Build & push image run: | docker build -t ghcr.io/my-org/my-app:${{ github.sha }} . docker push ghcr.io/my-org/my-app:${{ github.sha }} - name: Sign image (keyless — requires id-token: write permission) env: COSIGN_EXPERIMENTAL: "1" run: | cosign sign \ --yes \ ghcr.io/my-org/my-app:${{ github.sha }}

يتجاوز الخيار --yes موجه التأكيد التفاعلي، وهو مطلوب في بيئة CI. يُفعّل COSIGN_EXPERIMENTAL=1 الوضع بلا مفاتيح (وهو الآن الافتراضي في Cosign v2، لكنه لا يزال شائعاً في الأنابيب القديمة). بعد هذه الخطوة، يُخزَّن التوقيع كـ artifact OCI منفصل في نفس نطاق السجل — تستخدم Cosign الاصطلاح sha256-<digest>.sig.

للتحقق من التوقيع محلياً أو في سكريبت:

# التحقق من التوقيع بلا مفاتيح من workflow في GitHub Actions cosign verify \ --certificate-identity-regexp "^https://github.com/my-org/my-app/.github/workflows/.*" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ ghcr.io/my-org/my-app:sha256-abc123 # المخرجات عند النجاح — مصفوفة JSON من الحمولات المُتحقَّق منها: # [{"critical":{"identity":{"docker-reference":"ghcr.io/my-org/my-app"}, # "image":{"docker-manifest-digest":"sha256:abc123"},...},...}]
ممارسة إنتاجية: اربط --certificate-identity-regexp بمسار ملف workflow محدد (مثل .github/workflows/release.yml) لا بحرف بدل. سيقبل التعبير العام مثل .* التوقيعات من أي workflow في المستودع، بما فيها workflows طلبات السحب التي تُشغّل كوداً غير موثوق. قفل مُصدر OIDC بالغ الأهمية أيضاً — دوماً حدّد --certificate-oidc-issuer صراحةً.

تطبيق التوقيعات في وحدة تحكم Admission في Kubernetes

التوقيع بدون تحقق إلزامي على حدود الكلاستر مجرد مسرحية أمنية. طبقة التطبيق هي admission webhook في Kubernetes يعترض كل طلب إنشاء/تحديث Pod ويرفضه إذا كانت الصورة تفتقر إلى توقيع Cosign صالح. خياران ناضجان على نطاق الشركات الكبرى:

  • Policy Controller (من Sigstore) — admission webhook مستقل يقرأ موارد ClusterImagePolicy. المسار الموصى به للإعدادات الخالصة من Sigstore.
  • Kyverno — محرك سياسات Kubernetes عام مع تحقق Cosign مدمج؛ يُفضَّل عند استخدام Kyverno مسبقاً لسياسات أخرى.

المثال التالي يستخدم Policy Controller من Sigstore:

# تثبيت Policy Controller عبر Helm helm repo add sigstore https://sigstore.github.io/helm-charts helm install policy-controller sigstore/policy-controller \ --namespace cosign-system --create-namespace \ --set config.namespace=prod-namespace --- # ClusterImagePolicy: طلب توقيعات بلا مفاتيح من workflow CI الخاص بنا apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: require-signed-images spec: images: - glob: "ghcr.io/my-org/**" authorities: - keyless: url: https://fulcio.sigstore.dev identities: - issuer: https://token.actions.githubusercontent.com subjectRegExp: "https://github.com/my-org/.*/.github/workflows/release.yml@refs/heads/main" ctlog: url: https://rekor.sigstore.dev

بتطبيق هذه السياسة على prod-namespace، يُرفض أي Pod يشير إلى صورة من ghcr.io/my-org/** لا تحمل توقيعاً صالحاً من workflow CI المحدد بخطأ 403 Forbidden قبل بدء أي عملية حاوية. لا تتأثر namespaces غير المُصنَّفة للتطبيق — نمط شائع هو التطبيق في prod أولاً ثم التوسع إلى بيئة التدريج.

مشكلة إنتاجية — تثبيت الـ digest: توقيع تاغ (مثل :latest أو :1.2.3) غير كافٍ لأن التاغات قابلة للتغيير. يمكن للمهاجم القادر على الدفع إلى السجل نقل التاغ إلى صورة غير موقّعة. وقّع وتحقق دوماً بالـ digest (sha256:...). في Kubernetes، اضبط أداة CD الخاصة بك (ArgoCD أو Flux) لحل وتثبيت الـ digest قبل أن تراه وحدة تحكم الـ Admission في مواصفة الـ pod. تُوقّع Cosign على digest المانيفست؛ تتحقق Policy Controller من الـ digest — ما دمت لا تستخدم تاغات قابلة للتغيير من طرف إلى طرف، أنت محمي.

التحقق من الـ Attestations: ما وراء التوقيع

يمكن لـ Cosign أيضاً إرفاق والتحقق من attestations منظمة — بيانات وصفية موقّعة عن الصورة مثل وثائق SBOM وسجلات استمرارية SLSA ونتائج فحص الثغرات. هكذا تُثبت الشركات الكبرى ليس فقط "هذه الصورة موقّعة" بل "هذه الصورة بُنيت من الـ commit X، اجتازت SAST بدون ثغرات حرجة، وSBOM الخاص بها مُرفق." يتعامل الأمر cosign attest والأمر cosign verify-attestation مع هذا الـ workflow باستخدام تنسيق in-toto attestation:

# إرفاق attestation استمرارية SLSA موقّعة cosign attest \ --yes \ --predicate provenance.json \ --type slsaprovenance \ ghcr.io/my-org/my-app@sha256:abc123 # التحقق من الـ attestation وقت النشر cosign verify-attestation \ --type slsaprovenance \ --certificate-identity-regexp "^https://github.com/my-org/.*" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ ghcr.io/my-org/my-app@sha256:abc123 \ | jq '.payload | @base64d | fromjson'
الفكرة الجوهرية: يمكن لـ Policy Controller في Kubernetes تطبيق متطلبات الـ attestation أيضاً — ليس فقط التوقيعات. يمكن أن يتضمن كتلة authorities في ClusterImagePolicy قسم attestations يشترط predicate استمرارية SLSA صالح أو نتيجة فحص Trivy بدون ثغرات حرجة أو أي predicate مخصص. يحوّل هذا وحدة تحكم الـ Admission إلى بوابة سلسلة توريد كاملة لا مجرد خانة اختيار توقيع.

أوضاع الفشل والاعتبارات التشغيلية

يُضيف التحقق من التوقيع زمن استجابة لجدولة الـ pod (يجب أن يصل الـ admission webhook إلى السجل واختيارياً إلى Rekor). على نطاق Google يكون هذا عادةً 50–200 ميلي ثانية لكل digest صورة فريد، مع تخزين مؤقت مكثف من جانب Policy Controller. ثلاثة اعتبارات تشغيلية يجب التخطيط لها:

  • توافر الـ Webhook: إذا كان pod Policy Controller معطلاً، ستفشل الـ pods في الجدولة (سلوك الإغلاق عند الفشل). انشره بنسختين على الأقل وPodDisruptionBudget وتقارب نود يبقيه على نودات control-plane أو نودات نظام مخصصة.
  • بيئة معزولة عن الشبكة: في البيئات المعزولة، يكون Rekor وFulcio غير متاحَين. إما تشغيل stacks Sigstore مستضافة ذاتياً (sigstore-the-hard-way أو Helm chart sigstore/scaffolding)، أو استخدام التوقيع المستند إلى مفاتيح مع Hardware Security Module (HSM) بدلاً من الوضع بلا مفاتيح.
  • استثناء التمهيد: يجب استثناء صورة Policy Controller ذاتها من سياستها الخاصة لتجنب التبعية الدائرية أثناء تمهيد الكلاستر. أضف دوماً استثناء namespace أو صورة صريحاً لـ cosign-system.