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"