Cosign
Client CLI pour signer et vérifier images, blobs, SBOM. C’est l’outil que vous utilisez directement.
Mise à jour :
Vous avez construit une image conteneur, scanné ses vulnérabilités, généré un SBOM. Mais comment prouver que cette image provient bien de votre pipeline CI/CD et n’a pas été modifiée après le build ? C’est exactement ce que résout Cosign : signer cryptographiquement vos artefacts pour garantir leur authenticité et leur intégrité.
Dans le contexte SLSA, Cosign est l’outil qui permet d’atteindre Build L2 (provenance signée) : la signature cryptographique prouve que l’artefact a été construit par une plateforme de confiance et n’a pas été altéré.
Sans signature, rien ne prouve qu’une image mon-app:v2.0.0 :
Scénario d’attaque : un attaquant compromet votre registry Docker ou intercepte le push. Il remplace votre image par une version malveillante avec le même tag. Vos clusters Kubernetes déploient cette image corrompue en toute confiance.
La signature établit une chaîne de confiance vérifiable :
Cosign fait partie de Sigstore, un projet de l’OpenSSF qui fournit une infrastructure complète de signature :
Cosign
Client CLI pour signer et vérifier images, blobs, SBOM. C’est l’outil que vous utilisez directement.
Fulcio
Autorité de certification qui émet des certificats éphémères basés sur votre identité OIDC.
Rekor
Log de transparence immuable qui enregistre chaque signature avec preuve temporelle.
Sigstore Policy Controller
Admission controller Kubernetes qui vérifie les signatures avant d’autoriser le déploiement des pods.
Pour comprendre en détail l’architecture et les concepts de signature keyless, consultez le guide Sigstore.
Cosign 2.x privilégie le mode keyless (sans gestion de clés) pour la plupart des cas d’usage.
En mode keyless, vous n’avez pas de clé privée à gérer. Cosign utilise votre identité OIDC (GitHub Actions, GitLab CI, Google Cloud) pour obtenir un certificat éphémère de Fulcio.
Avantages :
Les clés restent utiles pour :
# Télécharger la dernière versionCOSIGN_VERSION=$(curl -s https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d '"' -f 4)curl -LO "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
# Installerchmod +x cosign-linux-amd64sudo mv cosign-linux-amd64 /usr/local/bin/cosign
# Vérifiercosign version# Avec Homebrewbrew install cosign
# Vérifiercosign version- name: Install Cosign uses: sigstore/cosign-installer@v3.7.0Vérifiez l’installation :
cosign version# Exemple sortie :# GitVersion: v2.4.1# GitCommit: 9a4cfe1aae777984c07ce373d97a65428bbff734# Platform: linux/amd64Dans un workflow GitHub Actions, la signature keyless utilise automatiquement le token OIDC du job :
name: Build and Sign
on: push: tags: ['v*']
# Permissions minimales au top-levelpermissions: contents: read
jobs: build-sign: runs-on: ubuntu-24.04
# Permissions spécifiques au job permissions: contents: read packages: write # Pour push vers GHCR id-token: write # Pour OIDC Sigstore attestations: write # Pour GitHub Attestations
steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to GHCR uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: ghcr.io/${{ github.repository }} tags: | type=semver,pattern=v{{version}}
- name: Build and push id: build uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max provenance: true # Génère l'attestation SLSA sbom: true # Génère le SBOM avec Syft
- name: Generate SLSA attestation uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true
- name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- name: Sign image with Cosign run: | IMAGE="ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}" echo "Signing: ${IMAGE}" cosign sign --yes "${IMAGE}"
# Vérification immédiate de la signature echo "Verifying signature..." cosign verify \ --certificate-identity-regexp="https://github.com/${{ github.repository }}" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ "${IMAGE}"Pour tester localement, Cosign ouvre un navigateur pour l’authentification OIDC :
# Pousser une image de testdocker build -t ghcr.io/mon-org/test:dev .docker push ghcr.io/mon-org/test:dev
# Récupérer le digestDIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/mon-org/test:dev | cut -d'@' -f2)
# Signer (ouvre le navigateur pour authentification)cosign sign --yes "ghcr.io/mon-org/test@${DIGEST}"La vérification s’assure que l’image a été signée par une identité de confiance.
# Vérifier avec contraintes d'identitécosign verify ghcr.io/mon-org/mon-app@sha256:abc123... \ --certificate-identity-regexp="^https://github.com/mon-org/mon-app" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com"Paramètres clés :
--certificate-identity-regexp : regex sur l’identité du signataire (URL du workflow)--certificate-oidc-issuer : l’émetteur OIDC attendu (GitHub Actions, GitLab, etc.)Sortie en cas de succès :
[{ "critical": { "identity": { "docker-reference": "ghcr.io/mon-org/mon-app" }, "image": { "docker-manifest-digest": "sha256:abc123..." }, "type": "cosign container image signature" }, "optional": { "Issuer": "https://token.actions.githubusercontent.com", "Subject": "https://github.com/mon-org/mon-app/.github/workflows/build.yml@refs/heads/main", ... }}]GHCR ne supporte pas encore complètement OCI 1.1 referrers. Les signatures Cosign ne sont pas attachées directement à l’image dans le registry, elles existent uniquement dans Rekor (le log de transparence).
#!/bin/bashset -e
IMAGE="ghcr.io/mon-org/mon-app@sha256:${DIGEST}"
if cosign verify "${IMAGE}" \ --certificate-identity-regexp="^https://github.com/mon-org/" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ > /dev/null 2>&1; then echo "✅ Signature valide"else echo "❌ Signature invalide ou absente" exit 1fiUn SBOM non signé peut être falsifié. Cosign permet d’attester un SBOM : le lier cryptographiquement à l’image avec une signature.
# Générer le SBOM avec Syftsyft ghcr.io/mon-org/mon-app@sha256:abc123 -o cyclonedx-json > sbom.cdx.json
# Attester le SBOM (keyless)cosign attest --yes \ --predicate sbom.cdx.json \ --type cyclonedx \ "ghcr.io/mon-org/mon-app@sha256:abc123"cosign verify-attestation \ --type cyclonedx \ --certificate-identity-regexp="^https://github.com/mon-org/" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/mon-org/mon-app@sha256:abc123jobs: build-and-sign: runs-on: ubuntu-24.04 permissions: contents: read packages: write id-token: write attestations: write
outputs: digest: ${{ steps.build.outputs.digest }} image: ghcr.io/${{ github.repository }}
steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to GHCR uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push id: build uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }} provenance: true sbom: true
- name: Generate SLSA attestation uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true
- name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- name: Sign image env: IMAGE: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }} run: | cosign sign --yes "${IMAGE}" cosign verify \ --certificate-identity-regexp="https://github.com/${{ github.repository }}" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ "${IMAGE}"
sbom-detailed: runs-on: ubuntu-24.04 needs: build-and-sign permissions: contents: read packages: read id-token: write
steps: - name: Install Syft uses: anchore/sbom-action/download-syft@a930d0ac434e3182448fe678398ba5713717112a # v0.21.0
- name: Generate SBOM (SPDX) run: | syft ${{ needs.build-and-sign.outputs.image }}@${{ needs.build-and-sign.outputs.digest }} \ -o spdx-json=sbom-spdx.json
- name: Generate SBOM (CycloneDX) run: | syft ${{ needs.build-and-sign.outputs.image }}@${{ needs.build-and-sign.outputs.digest }} \ -o cyclonedx-json=sbom-cyclonedx.json
- name: Upload SBOM artifacts uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: sbom path: | sbom-spdx.json sbom-cyclonedx.json
- name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- name: Attach SBOM to image run: | cosign attach sbom \ --sbom sbom-spdx.json \ ${{ needs.build-and-sign.outputs.image }}@${{ needs.build-and-sign.outputs.digest }}Pour les environnements nécessitant des clés gérées.
cosign generate-key-pair# Entrer une passphrase# Crée : cosign.key (privée), cosign.pub (publique)cosign sign --key cosign.key ghcr.io/mon-org/mon-app@sha256:abc123cosign verify --key cosign.pub ghcr.io/mon-org/mon-app@sha256:abc123# Générer la clé dans AWS KMScosign generate-key-pair --kms awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234
# Signercosign sign --key awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234 \ ghcr.io/mon-org/mon-app@sha256:abc123
# Vérifiercosign verify --key awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234 \ ghcr.io/mon-org/mon-app@sha256:abc123KMS supportés : AWS KMS (awskms://), GCP KMS (gcpkms://), Azure Key Vault
(azurekms://), HashiCorp Vault (hashivault://), Kubernetes Secrets (k8s://).
Kyverno peut bloquer le déploiement d’images non signées :
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: verify-image-signaturesspec: validationFailureAction: Enforce background: false rules: - name: verify-cosign-signature match: any: - resources: kinds: - Pod verifyImages: - imageReferences: - "ghcr.io/mon-org/*" attestors: - entries: - keyless: issuer: "https://token.actions.githubusercontent.com" subjectRegExp: "^https://github.com/mon-org/"# Installer le policy controllerhelm repo add sigstore https://sigstore.github.io/helm-chartshelm install policy-controller sigstore/policy-controller \ -n sigstore-system --create-namespaceGitHub fournit une API pour vérifier les attestations de provenance :
# Vérifier une image avec gh attestationgh attestation verify oci://ghcr.io/owner/repo:tag --owner ownerSortie en cas de succès :
Loaded digest sha256:abc123... for oci://ghcr.io/owner/repo:tagLoaded 2 attestations from GitHub API✓ Verification succeeded!
sha256:abc123... was attested by:REPOSITORY PREDICATE_TYPE WORKFLOWowner/repo https://slsa.dev/provenance/v1 .github/workflows/release.yml@refs/tags/v1.0.0Créez un workflow qui vérifie périodiquement vos attestations :
name: SLSA Verification
on: schedule: - cron: '0 2 * * *' # Toutes les nuits à 2h workflow_dispatch:
permissions: contents: read
jobs: verify: runs-on: ubuntu-24.04 steps: - name: Get latest tag id: latest run: | LATEST_TAG=$(gh api repos/${{ github.repository }}/tags --jq '.[0].name') echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get image digest id: digest run: | IMAGE="ghcr.io/${{ github.repository }}:${{ steps.latest.outputs.tag }}" DIGEST=$(docker pull "${IMAGE}" 2>&1 | grep "Digest:" | cut -d' ' -f2) echo "digest=$DIGEST" >> $GITHUB_OUTPUT
- name: Verify SLSA attestation run: | gh attestation verify \ oci://ghcr.io/${{ github.repository }}@${{ steps.digest.outputs.digest }} \ --owner ${{ github.repository_owner }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Report status if: always() run: | if [ "${{ job.status }}" == "success" ]; then echo "✅ SLSA verification passed for ${{ steps.latest.outputs.tag }}" else echo "❌ SLSA verification failed for ${{ steps.latest.outputs.tag }}" exit 1 fiKeyless par défaut
Privilégiez la signature keyless pour éviter la gestion de secrets. Les clés ne sont utiles que pour les environnements air-gapped.
Signer par digest
Toujours signer image@sha256:..., jamais image:tag. Un tag peut être
réassigné, pas un digest.
Vérifier l'identité
Utilisez --certificate-identity-regexp pour vérifier que le signataire
est bien votre pipeline CI/CD.
Automatiser la vérification
Déployez Kyverno ou Policy Controller pour bloquer les images non signées dans Kubernetes.
# Permissions minimales au niveau workflowpermissions: contents: read
jobs: build: # Permissions spécifiques au job permissions: contents: read # Lire le code packages: write # Pousser images et signatures id-token: write # Obtenir token OIDC pour Sigstore attestations: write # Générer attestations GitHubToujours utiliser des SHA de commit pour éviter les attaques supply chain :
steps: # ❌ Mauvais : version flottante - uses: actions/checkout@v4
# ✅ Bon : SHA pinné avec commentaire de version - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1Toujours vérifier la signature immédiatement après l’avoir créée :
- name: Sign and verify run: | IMAGE="ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}"
# Signer cosign sign --yes "${IMAGE}"
# Vérifier immédiatement cosign verify \ --certificate-identity-regexp="https://github.com/${{ github.repository }}" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ "${IMAGE}"Cause : l’image n’est pas signée ou les contraintes d’identité ne correspondent pas.
# Lister les signatures existantescosign triangulate ghcr.io/mon-org/mon-app@sha256:abc123
# Vérifier sans contraintes d'identité (debug)cosign verify ghcr.io/mon-org/mon-app@sha256:abc123 \ --certificate-identity-regexp=".*" \ --certificate-oidc-issuer-regexp=".*"Cause : permissions id-token: write manquantes dans le workflow.
permissions: id-token: write # Ajouter cette ligneCause : pas de fournisseur OIDC disponible.
Solution : utilisez l’authentification interactive ou des clés locales pour les tests.
# Mode interactif (ouvre navigateur)COSIGN_EXPERIMENTAL=1 cosign sign --yes ghcr.io/mon-org/test@sha256:abc123cosign attest pour les lier à l’image--certificate-identity-regexp