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é.
Pourquoi signer vos images conteneurs ?
Section intitulée « Pourquoi signer vos images conteneurs ? »Le problème : registries compromis et images altérées
Section intitulée « Le problème : registries compromis et images altérées »Sans signature, rien ne prouve qu’une image mon-app:v2.0.0 :
- A vraiment été construite par votre CI/CD (et pas par un attaquant)
- N’a pas été modifiée après le build (injection de code malveillant)
- Correspond au code source versionné (et pas à une version modifiée)
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 solution : signature cryptographique
Section intitulée « La solution : signature cryptographique »La signature établit une chaîne de confiance vérifiable :
- Authenticité : l’image provient bien de l’identité déclarée (votre pipeline)
- Intégrité : l’image n’a pas été modifiée depuis sa signature
- Non-répudiation : la signature est enregistrée dans un log public immuable
L’écosystème Sigstore
Section intitulée « L’écosystème Sigstore »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.
Keyless vs clés traditionnelles
Section intitulée « Keyless vs clés traditionnelles »Cosign v3.x privilégie le mode keyless (sans gestion de clés) pour la plupart des cas d’usage.
Signature keyless (recommandée)
Section intitulée « Signature keyless (recommandée) »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 :
- Pas de secrets à stocker ni à faire tourner
- Certificat valide uniquement quelques minutes (réduction de la surface d’attaque)
- Identité liée au pipeline CI/CD (traçabilité)
- Signature enregistrée dans Rekor (auditabilité)
Signature avec clés (cas spécifiques)
Section intitulée « Signature avec clés (cas spécifiques) »Les clés restent utiles pour :
- Environnements air-gapped sans accès à Sigstore public
- Politiques de conformité exigeant des clés gérées par l’organisation
- Intégration avec un KMS existant (AWS KMS, GCP KMS, Azure Key Vault, Vault)
Installation
Section intitulée « Installation »# 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: v3.0.4# GitCommit: (hash du commit)# Platform: linux/amd64Signer une image (keyless)
Section intitulée « Signer une image (keyless) »Prérequis
Section intitulée « Prérequis »- Une image poussée dans un registry OCI (GHCR, Docker Hub, ECR, GCR, Harbor…)
- Un fournisseur OIDC configuré (GitHub Actions, GitLab CI, Google Cloud, etc.)
Signature en CI/CD (GitHub Actions)
Section intitulée « Signature en CI/CD (GitHub Actions) »Dans 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}"Signature interactive (développement)
Section intitulée « Signature interactive (développement) »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}"Vérifier une signature
Section intitulée « Vérifier une signature »La vérification s’assure que l’image a été signée par une identité de confiance.
Vérifier une image signée en keyless
Section intitulée « Vérifier une image signée en keyless »# 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", ... }}]Cas particulier : GitHub Container Registry (GHCR)
Section intitulée « Cas particulier : GitHub Container Registry (GHCR) »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).
Vérifier dans un script CI
Section intitulée « Vérifier dans un script CI »#!/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 1fiAttacher un SBOM signé
Section intitulée « Attacher un SBOM signé »Un SBOM non signé peut être falsifié. Cosign permet d’attester un SBOM : le lier cryptographiquement à l’image avec une signature.
Générer et attester un SBOM
Section intitulée « Générer et attester un SBOM »# 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"Vérifier une attestation SBOM
Section intitulée « Vérifier une attestation SBOM »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:abc123Workflow complet : build, sign, attest
Section intitulée « Workflow complet : build, sign, attest »jobs: 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 }}Signature avec clés
Section intitulée « Signature avec clés »Pour les environnements nécessitant des clés gérées.
Générer une paire de clés
Section intitulée « Générer une paire de clés »cosign generate-key-pair# Entrer une passphrase# Crée : cosign.key (privée), cosign.pub (publique)Signer avec une clé
Section intitulée « Signer avec une clé »cosign sign --key cosign.key ghcr.io/mon-org/mon-app@sha256:abc123Vérifier avec la clé publique
Section intitulée « Vérifier avec la clé publique »cosign verify --key cosign.pub ghcr.io/mon-org/mon-app@sha256:abc123Utiliser un KMS
Section intitulée « Utiliser un KMS »# 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://).
Vérifier les signatures dans Kubernetes
Section intitulée « Vérifier les signatures dans Kubernetes »Avec Kyverno
Section intitulée « Avec Kyverno »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/"Avec Sigstore Policy Controller
Section intitulée « Avec Sigstore Policy Controller »# Installer le policy controllerhelm repo add sigstore https://sigstore.github.io/helm-chartshelm install policy-controller sigstore/policy-controller \ -n sigstore-system --create-namespaceVérifier les attestations SLSA
Section intitulée « Vérifier les attestations SLSA »GitHub fournit une API pour vérifier les attestations de provenance :
Vérification avec GitHub CLI
Section intitulée « Vérification avec GitHub CLI »# 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.0Workflow de vérification automatique
Section intitulée « Workflow de vérification automatique »Cré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 fiSécurité
Section intitulée « Sécurité »Bonnes pratiques
Section intitulée « Bonnes pratiques »Keyless 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 GitHub Actions
Section intitulée « Permissions GitHub Actions »# 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 GitHubActions GitHub pinnées par SHA
Section intitulée « Actions GitHub pinnées par SHA »Toujours 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.1Vérification post-signature
Section intitulée « Vérification post-signature »Toujours 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}"Dépannage
Section intitulée « Dépannage »”no matching signatures”
Section intitulée « ”no matching signatures” »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=".*"”OIDC token request failed”
Section intitulée « ”OIDC token request failed” »Cause : permissions id-token: write manquantes dans le workflow.
permissions: id-token: write # Ajouter cette ligneSignature échoue en local
Section intitulée « Signature échoue en local »Cause : 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:abc123Tableau récapitulatif des erreurs
Section intitulée « Tableau récapitulatif des erreurs »| Erreur | Cause | Solution |
|---|---|---|
no matching signatures | Image non signée ou contraintes d’identité incorrectes | Vérifier avec --certificate-identity-regexp=".*" pour debug |
OIDC token request failed | Permission id-token: write manquante | Ajouter la permission au job |
bundle verification failed | Ancienne version Cosign (< 2.6.2 / 3.0.4) | Mettre à jour immédiatement (GHSA-whqx-f9j3-ch6m) |
certificate identity mismatch | Le workflow a changé (nom, branche) | Adapter le regexp ou re-signer |
Rekor entry not found | Signature avec --tlog-upload=false | Utiliser --insecure-ignore-tlog pour vérifier (non recommandé) |
À retenir
Section intitulée « À retenir »- Cosign signe cryptographiquement les images pour prouver leur authenticité
- Le mode keyless (recommandé) utilise OIDC + Fulcio + Rekor, sans clés à gérer
- Signez toujours par digest, pas par tag
- Mettez à jour vers v3.0.4 pour corriger GHSA-whqx-f9j3-ch6m
- Attestez vos SBOM avec
cosign attestpour les lier à l’image - Vérifiez l’identité du signataire avec
--certificate-identity-regexp - Déployez Kyverno ou Policy Controller pour bloquer les images non signées
- Cosign répond aux exigences SLSA Build L2 (provenance signée)
Liens utiles
Section intitulée « Liens utiles »- SLSA : sécuriser la chaîne d’approvisionnement — framework de maturité supply chain
- SBOM : inventaire logiciel — générer et exploiter un SBOM
- Syft — générateur de SBOM
- Grype — scanner de vulnérabilités
- VEX — qualifier l’exploitabilité des CVE
Questions fréquentes
Section intitulée « Questions fréquentes »Cosign : signature cryptographique pour conteneurs
Cosign est un outil CLI développé par le projet Sigstore pour signer et vérifier cryptographiquement les images conteneurs et artefacts OCI.Ce que Cosign garantit
- Authenticité : l'image provient bien de l'identité déclarée (votre pipeline CI/CD)
- Intégrité : l'image n'a pas été modifiée depuis sa signature
- Non-répudiation : la signature est enregistrée dans un log public immuable (Rekor)
Cas d'usage principal
Vous construisez une imagemon-app:v2.0.0 dans GitHub Actions. Sans signature, rien ne prouve que cette image n'a pas été remplacée par un attaquant dans le registry. Avec Cosign, vous signez l'image par son digest et vérifiez cette signature avant déploiement.# Signer (en CI/CD keyless)
cosign sign --yes ghcr.io/mon-org/app@sha256:abc123
# Vérifier avant déploiement
cosign verify ghcr.io/mon-org/app@sha256:abc123 \
--certificate-identity-regexp="^https://github.com/mon-org/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
Keyless vs clés traditionnelles
| Aspect | Keyless (recommandé) | Avec clés |
|---|---|---|
| Gestion secrets | Aucune clé à stocker | Clé privée à sécuriser |
| Durée validité | Certificat ~10 min | Clé longue durée |
| Traçabilité | Identité liée au pipeline | Clé partageable |
| Infrastructure | Sigstore public | Possible air-gapped |
| Auditabilité | Log Rekor public | Optionnelle |
Signature keyless
Cosign utilise votre identité OIDC (GitHub Actions, GitLab CI, Google Cloud) pour obtenir un certificat éphémère de Fulcio :# En GitHub Actions (automatique)
cosign sign --yes ghcr.io/org/app@sha256:abc123
Avantages :- Pas de secrets à stocker ni à faire tourner
- Certificat valide uniquement quelques minutes
- Signature enregistrée dans Rekor
Signature avec clés
Utile pour :- Environnements air-gapped sans accès à Sigstore
- Politiques de conformité exigeant des clés gérées
- Intégration avec un KMS (AWS KMS, GCP KMS, Vault)
# Générer une paire de clés
cosign generate-key-pair
# Signer
cosign sign --key cosign.key ghcr.io/org/app@sha256:abc123
Workflow GitHub Actions complet
jobs:
build-sign:
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write # Push vers GHCR
id-token: write # OIDC pour Sigstore
attestations: write # Attestations GitHub
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- uses: sigstore/cosign-installer@v3
- name: Sign image
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
Points clés
- Permission
id-token: write: obligatoire pour obtenir le token OIDC - Signer par digest (
@sha256:...), jamais par tag --yes: accepte automatiquement les conditions d'utilisation
Vérification avec contraintes d'identité
cosign verify ghcr.io/mon-org/app@sha256:abc123 \
--certificate-identity-regexp="^https://github.com/mon-org/mon-app" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
Paramètres essentiels
| Paramètre | Usage | Exemple |
|---|---|---|
--certificate-identity-regexp |
Regex sur l'identité du signataire | ^https://github.com/org/ |
--certificate-oidc-issuer |
Émetteur OIDC attendu | https://token.actions.githubusercontent.com |
--key |
Clé publique (si signature avec clés) | cosign.pub |
Sortie en cas de succès
[{
"critical": {
"identity": { "docker-reference": "ghcr.io/mon-org/app" },
"image": { "docker-manifest-digest": "sha256:abc123..." }
},
"optional": {
"Issuer": "https://token.actions.githubusercontent.com",
"Subject": "https://github.com/mon-org/app/.github/workflows/build.yml@refs/heads/main"
}
}]
En cas d'échec
# Debug : vérifier sans contraintes
cosign verify ghcr.io/mon-org/app@sha256:abc123 \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer-regexp=".*"
Le problème GHCR
GitHub Container Registry ne retourne pas les OCI referrers, donc Cosign ne trouve pas la signature attachée à l'image, même si elle existe dans Rekor.Solutions
1. GitHub Attestations API (recommandé)gh attestation verify oci://ghcr.io/owner/app:tag --owner owner
2. Vérification keyless complèteFonctionne si vous spécifiez explicitement l'identité :cosign verify ghcr.io/owner/app@sha256:abc123 \
--certificate-identity-regexp="^https://github.com/owner/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
3. Mode expérimental RekorCOSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/owner/app:tag
⚠️ Attention : vérifie qu'une signature existe, mais sans contraintes d'identité.Pour Kubernetes
Kyverno fonctionne avec GHCR car il interroge directement Rekor :verifyImages:
- imageReferences: ["ghcr.io/owner/*"]
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subjectRegExp: "^https://github.com/owner/"
Attester un SBOM
Un SBOM non signé peut être falsifié. L'attestation lie cryptographiquement le SBOM à l'image.# 1. Générer le SBOM
syft ghcr.io/org/app@sha256:abc123 -o cyclonedx-json > sbom.cdx.json
# 2. Attester (keyless)
cosign attest --yes \
--predicate sbom.cdx.json \
--type cyclonedx \
ghcr.io/org/app@sha256:abc123
Vérifier l'attestation
cosign verify-attestation \
--type cyclonedx \
--certificate-identity-regexp="^https://github.com/org/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/org/app@sha256:abc123
Types d'attestation supportés
| Type | Format SBOM | Usage |
|---|---|---|
cyclonedx |
CycloneDX JSON | Standard DevSecOps |
spdx |
SPDX JSON | Standard NTIA |
vuln |
Résultats scan | Grype, Trivy |
custom |
Format libre | Métadonnées custom |
GHSA-whqx-f9j3-ch6m (CVE-2026-22703)
Sévérité : Modérée (CVSS 5.5)Impact
Un bundle Cosign pouvait être fabriqué pour valider une signature même si l'entrée Rekor embarquée ne référençait pas le bon digest, signature ou clé publique.Un attaquant ayant compromis votre identité ou clé de signature pouvait construire un bundle valide avec une entrée Rekor arbitraire, empêchant l'audit de l'événement de signature.Versions affectées
| Branche | Affectées | Corrigées |
|---|---|---|
| v2.x | ≤ 2.6.1 | 2.6.2 |
| v3.x | ≤ 3.0.3 | 3.0.4 |
Non affectés
Utilisateurs de Cosign v3 avec les flags par défaut :--use-signing-config=true--new-bundle-format=true
Action immédiate
# Mettre à jour
brew upgrade cosign
# ou
curl -sL https://github.com/sigstore/cosign/releases/download/v3.0.4/cosign-linux-amd64 \
-o /usr/local/bin/cosign && chmod +x /usr/local/bin/cosign
# Vérifier la version
cosign version
Avec Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
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/"
Avec Sigstore Policy Controller
# Installer
helm repo add sigstore https://sigstore.github.io/helm-charts
helm install policy-controller sigstore/policy-controller \
-n sigstore-system --create-namespace
Effet
- Enforce : bloque le déploiement si signature absente ou invalide
- Audit : log les violations sans bloquer (mode test)
Utilisation avec AWS KMS
KMS permet une gestion centralisée des clés avec audit, rotation et contrôle IAM.Créer la clé dans KMS
# Via AWS CLI
aws kms create-key \
--description "Cosign signing key" \
--tags TagKey=Purpose,TagValue=ImageSigning
# Noter l'ARN : arn:aws:kms:eu-west-1:123456789:key/abcd-1234
# Ou via Cosign
cosign generate-key-pair --kms awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234
Signer avec KMS
cosign sign --key awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234 \
ghcr.io/mon-org/app@sha256:abc123
Vérifier
cosign verify --key awskms://arn:aws:kms:eu-west-1:123456789:key/abcd-1234 \
ghcr.io/mon-org/app@sha256:abc123
KMS supportés
| Provider | Préfixe URI | Exemple |
|---|---|---|
| AWS KMS | awskms:// |
awskms://arn:aws:kms:... |
| GCP KMS | gcpkms:// |
gcpkms://projects/.../keys/... |
| Azure Key Vault | azurekms:// |
azurekms://vault/key |
| HashiCorp Vault | hashivault:// |
hashivault://transit/keys/cosign |
Cosign vs Notation
| Critère | Cosign (Sigstore) | Notation (Notary v2) |
|---|---|---|
| Mode principal | Keyless (OIDC) | Clés X.509 |
| Log de transparence | Rekor (public) | Optionnel |
| Gestion de clés | Aucune (keyless) | PKI traditionnelle |
| Certificats | Éphémères (Fulcio) | Longue durée |
| Écosystème | Sigstore, Linux Foundation | CNCF, Microsoft, AWS |
| Cas d'usage | CI/CD cloud-native | Entreprise, air-gapped |
Quand utiliser Cosign ?
- Pipelines GitHub Actions, GitLab CI, Google Cloud Build
- Environnements cloud-native avec accès internet
- Besoin d'auditabilité publique (Rekor)
- Pas envie de gérer des clés
Quand utiliser Notation ?
- Environnements air-gapped sans accès à Sigstore
- Politique de conformité exigeant une PKI interne
- Intégration avec Azure Container Registry native
- Chaîne de confiance X.509 existante