Cosign في CI: وقّع صور Docker قبل النشر
هتكسب gate واضح يمنع نشر صورة Docker غير موثقة، بدل ما تعتمد على tag ممكن يتغير بعد دقيقة.
مستوى القارئ: متوسط
المشكلة باختصار
الطريقة الشائعة بتفشل هنا: الـ pipeline يبني image، يدفعها إلى registry، ثم ينشر my-app:latest. المشكلة إن latest اسم قابل للتغيير. لو اتبدل أو اتدفع من workflow غلط، Kubernetes أو VM deployment مش هيعرف الفرق.
الافتراض إن عندك تطبيق Docker بيتنشر من GitHub Actions إلى registry مثل GHCR أو Docker Hub، وعندك deploy stage بعد البناء. لو الفريق صغير وفيه 3 مطورين فقط، توقيع الصور ممكن يبدو زيادة. لكن عند 10 مطورين أو أكثر، أو بيئة إنتاج فيها أكثر من service، gate التحقق بيمنع أخطاء مكلفة.
الفكرة بمثال بسيط
ركز في المثال ده. عندك فاتورة PDF جاية من المورد. وجود اسم المورد في عنوان الملف لا يكفي. الأفضل تشوف توقيعه الرقمي وتتأكد إن الملف نفسه لم يتغير. صورة Docker نفس الفكرة. الـ tag هو اسم الملف، والـ digest هو بصمة المحتوى، والتوقيع هو الدليل إن workflow معروف هو اللي أنتج البصمة دي.
علميًا، Cosign من Sigstore يوقّع artifact مثل container image. في وضع keyless، GitHub Actions يطلب OIDC token قصير العمر، وSigstore يربط التوقيع بهوية الـ workflow. بدل ما تخزن private key في GitHub Secrets، بتعتمد على هوية مؤقتة وسجل شفافية قابل للمراجعة.
Workflow عملي في GitHub Actions
أفضل طريقة هنا إنك تبني الصورة، تدفعها بالـ digest، توقع الـ digest، ثم تتحقق منه قبل deploy. لا توقع :latest كحقيقة نهائية. وقّع sha256 لأنه بيمثل محتوى الصورة بالظبط.
name: build-sign-verify
on:
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write
jobs:
image:
runs-on: ubuntu-latest
env:
IMAGE: ghcr.io/${{ github.repository }}/api
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
id: build
with:
context: .
push: true
tags: ${{ env.IMAGE }}:${{ github.sha }}
- uses: sigstore/cosign-installer@v3
- name: Sign image digest
run: |
cosign sign --yes "${IMAGE}@${{ steps.build.outputs.digest }}"
- name: Verify before deploy
run: |
cosign verify "${IMAGE}@${{ steps.build.outputs.digest }}" \
--certificate-identity-regexp "https://github.com/${{ github.repository }}/.github/workflows/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
النقطة المهمة هي id-token: write. من غيره، workflow مش هيقدر يستخدم OIDC بالشكل المطلوب للتوقيع keyless. ووجود --certificate-oidc-issuer في التحقق بيمنع قبول توقيع من مصدر عشوائي.
القياس المتوقع
في مشروع Node.js متوسط حجمه 450MB للصورة النهائية، بناء الصورة قد يأخذ 3 إلى 4 دقائق على GitHub-hosted runner. إضافة cosign sign وcosign verify غالبًا تضيف 20 إلى 60 ثانية. الرقم تقديري، ويتأثر بسرعة الـ registry وعدد الصور.
الـ trade-off هنا واضح. بتكسب traceability وسجل توقيع يمكن مراجعته، وتخسر أقل من دقيقة غالبًا في كل pipeline. لو deploy عندك مرة أو مرتين يوميًا، المكسب أعلى من التكلفة. لو عندك 200 image build في اليوم، لازم تجمع التوقيع في reusable workflow وتراقب زمنه.
إزاي تستخدمه بدون ما تعطل الفريق
- ابدأ على service واحدة غير حرجة، مثل admin API أو worker داخلي.
- وقّع digest فقط، وليس tag عام مثل
latest. - اجعل التحقق خطوة قبل deploy، وليس بعده.
- اكتب identity regex ضيق للـ repository والـ workflow المستخدمين في الإنتاج.
- سجل زمن
signوverifyلمدة أسبوع قبل تعميمه.
لو عايزها تدعم أكثر من registry، خليك صريح في naming. مثال: ghcr.io/org/app@sha256:... وregistry.example.com/app@sha256:.... لا تخلط نفس التوقيع مع artifact مختلف.
متى لا تستخدم هذه الطريقة
لا تستخدم Cosign كبديل لفحص الثغرات. التوقيع لا يقول إن الصورة آمنة من CVEs، بل يقول إن الصورة جاءت من مصدر معروف ولم تتغير. لا تبدأ به أيضًا لو pipeline نفسه غير مستقر ويفشل يوميًا بسبب tests أو build cache. أصلح الاستقرار الأول، ثم أضف gate التوقيع.
كذلك، لو عندك بيئة offline بالكامل ولا يمكنها الوصول إلى سجل الشفافية أو خدمات Sigstore العامة، ستحتاج تصميم مختلف: مفاتيح KMS داخلية أو mirror موثوق. هذا يزيد الإدارة والتكلفة، لكنه مناسب لبعض البيئات الحساسة.
مصادر اعتمد عليها المقال
الخطوة التالية
افتح workflow البناء الحالي، وبدل deploy المباشر بخطوتين: cosign sign بعد push، ثم cosign verify قبل deploy. لو التحقق فشل، لا تنشر الصورة.