Aller au contenu
CI/CD & Automatisation medium

Vérifier les attestations GitHub

18 min de lecture

Générer des attestations n'a de valeur que si elles sont vérifiées. Ce guide explique comment valider la provenance de vos artefacts avant de les déployer.

  • Vérifier un artefact avec gh attestation verify
  • Bloquer un déploiement non vérifié dans un workflow CI/CD
  • Utiliser cosign et slsa-verifier pour la vérification SLSA
  • Afficher le niveau SLSA atteint via un badge README
  • Imposer la vérification à l'admission dans Kubernetes

Vérifier une attestation, c'est s'assurer qu'un artefact est bien celui qu'on croit. Une attestation prouve :

  • L'artefact vient du bon repository
  • Il a été construit par le bon workflow
  • Le code source correspond au commit attendu
  • La signature est valide (pas de modification)

Sans vérification, un attaquant pourrait substituer un artefact malveillant sans que rien ne le signale.

La CLI GitHub (gh) est le moyen le plus direct de vérifier une attestation : elle interroge l'API GitHub et valide la signature Sigstore.

Installez la CLI selon votre système :

Fenêtre de terminal
# macOS
brew install gh
# Linux (Debian/Ubuntu)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list
sudo apt update && sudo apt install gh

Voir le guide GitHub CLI pour l'installation complète et l'authentification.

Pointez la commande sur le fichier téléchargé et indiquez le dépôt attendu :

Fenêtre de terminal
# Vérifier un artefact téléchargé
gh attestation verify my-app-v1.0.0.tar.gz --owner owner --repo repo

Sortie en cas de succès :

Loaded digest sha256:abc123... for file my-app-v1.0.0.tar.gz
Loaded 1 attestation from GitHub API
✓ Verification succeeded!
SHA256 digest: abc123...
Sigstore Bundle: present
Certificate Subject: https://github.com/owner/repo/.github/workflows/release.yml@refs/tags/v1.0.0
Certificate Issuer: https://token.actions.githubusercontent.com

Pour une image, préfixez la référence par oci:// :

Fenêtre de terminal
# Vérifier une image depuis un registry
gh attestation verify oci://ghcr.io/owner/app:v1.0.0 --owner owner

Plusieurs options resserrent la vérification — digest exact, workflow signataire, sortie machine :

Fenêtre de terminal
# Vérifier avec un digest spécifique
gh attestation verify --digest sha256:abc123... --owner owner --repo repo
# Vérifier que le workflow source est correct
gh attestation verify my-app.tar.gz \
--owner owner \
--repo repo \
--signer-workflow release.yml
# Format JSON pour traitement automatisé
gh attestation verify my-app.tar.gz --owner owner --format json

La vérification prend tout son sens automatisée : un déploiement ne doit jamais consommer un artefact dont la provenance n'a pas été validée.

Ce workflow télécharge un artefact, vérifie son attestation, et ne déploie que si la vérification réussit.

name: Deploy
on:
workflow_dispatch:
inputs:
version:
description: 'Version to deploy'
required: true
permissions: {}
jobs:
deploy:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Télécharger l'artefact
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
run: |
gh release download "$VERSION" \
--repo "$GITHUB_REPOSITORY" \
--pattern "my-app-*.tar.gz"
- name: Vérifier l'attestation
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh attestation verify my-app-*.tar.gz \
--owner "$GITHUB_REPOSITORY_OWNER" \
--repo "${GITHUB_REPOSITORY#*/}"
- name: Déployer (uniquement si la vérification a réussi)
run: ./deploy.sh my-app-*.tar.gz

Même principe pour une image : on valide l'attestation avant le docker pull.

name: Deploy Image
on:
workflow_dispatch:
inputs:
version:
description: 'Version to deploy'
required: true
permissions: {}
jobs:
deploy:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Vérifier l'attestation de l'image
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
run: |
gh attestation verify \
"oci://ghcr.io/$GITHUB_REPOSITORY:$VERSION" \
--owner "$GITHUB_REPOSITORY_OWNER"
- name: Pull et déploiement
env:
VERSION: ${{ inputs.version }}
run: |
docker pull "ghcr.io/$GITHUB_REPOSITORY:$VERSION"
kubectl set image deployment/app "app=ghcr.io/$GITHUB_REPOSITORY:$VERSION"

cosign est l'outil de signature de Sigstore, utilisé en coulisse par les attestations GitHub.

Installez le binaire cosign :

Fenêtre de terminal
# macOS
brew install cosign
# Linux
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
sudo install cosign-linux-amd64 /usr/local/bin/cosign

cosign verify contrôle la signature keyless contre l'identité du workflow attendu :

Fenêtre de terminal
# Vérifier avec les certificats Sigstore
cosign verify \
--certificate-identity-regexp="https://github.com/owner/repo/.github/workflows/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/owner/app:v1.0.0

cosign verify-attestation valide en plus le prédicat de provenance SLSA :

Fenêtre de terminal
# Télécharger et vérifier l'attestation
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity-regexp="https://github.com/owner/repo/.github/workflows/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/owner/app:v1.0.0

slsa-verifier est l'outil officiel SLSA pour vérifier les attestations de provenance.

Installez-le via Go ou en téléchargeant le binaire :

Fenêtre de terminal
# Go install
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
# Ou télécharger le binaire
curl -LO https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64
chmod +x slsa-verifier-linux-amd64
sudo mv slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier

Téléchargez l'artefact et son fichier de provenance, puis lancez la vérification :

Fenêtre de terminal
# Télécharger l'artefact et son attestation depuis une release
gh release download v1.0.0 --repo owner/repo --pattern "*.tar.gz"
gh release download v1.0.0 --repo owner/repo --pattern "*.intoto.jsonl"
# Vérifier avec slsa-verifier
slsa-verifier verify-artifact my-app-v1.0.0.tar.gz \
--provenance-path my-app-v1.0.0.intoto.jsonl \
--source-uri github.com/owner/repo \
--source-tag v1.0.0

Sortie en cas de succès :

Verified signature against tlog entry index 12345 at URL https://rekor.sigstore.dev
Verified build using builder "https://github.com/actions/runner" at commit abc123def456
SLSA verification passed
✓ Verification succeeded!

Pour une image, préférez la vérification par digest plutôt que par tag :

Fenêtre de terminal
# Vérifier une image GHCR avec tag
slsa-verifier verify-image ghcr.io/owner/app:v1.0.0 \
--source-uri github.com/owner/repo \
--source-tag v1.0.0
# Vérifier avec un digest (plus sûr)
slsa-verifier verify-image ghcr.io/owner/app@sha256:abc123... \
--source-uri github.com/owner/repo \
--source-tag v1.0.0

L'option --print-provenance affiche le prédicat complet, dont le buildType qui détermine le niveau SLSA :

Fenêtre de terminal
# Vérifier et afficher le niveau SLSA en mode verbeux
slsa-verifier verify-image ghcr.io/owner/app:v1.0.0 \
--source-uri github.com/owner/repo \
--source-tag v1.0.0 \
--print-provenance

Sortie détaillée :

{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "ghcr.io/owner/app",
"digest": {"sha256": "abc123..."}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1"
}
}
}

Niveau SLSA :

buildTypeNiveau SLSA
workflow/v1SLSA L3
workflow/v0SLSA L2

Ce script enchaîne la vérification et l'affichage du niveau SLSA, et échoue proprement en cas de problème :

#!/bin/bash
# verify-slsa.sh - Vérifier et afficher le niveau SLSA
set -e
IMAGE="$1"
SOURCE_URI="$2"
SOURCE_TAG="$3"
if [ -z "$IMAGE" ] || [ -z "$SOURCE_URI" ] || [ -z "$SOURCE_TAG" ]; then
echo "Usage: $0 <image> <source-uri> <source-tag>"
echo "Exemple: $0 ghcr.io/owner/app:v1.0.0 github.com/owner/repo v1.0.0"
exit 1
fi
echo "Vérification SLSA de $IMAGE"
echo ""
# Vérifier avec slsa-verifier
if slsa-verifier verify-image "$IMAGE" \
--source-uri "$SOURCE_URI" \
--source-tag "$SOURCE_TAG" \
--print-provenance > /tmp/slsa-provenance.json 2>&1; then
echo "Vérification SLSA réussie"
echo ""
# Extraire le niveau SLSA
BUILD_TYPE=$(jq -r '.predicate.buildDefinition.buildType' /tmp/slsa-provenance.json)
echo "Niveau SLSA atteint :"
if [[ "$BUILD_TYPE" == *"workflow/v1"* ]]; then
echo " SLSA Level 3"
elif [[ "$BUILD_TYPE" == *"workflow/v0"* ]]; then
echo " SLSA Level 2"
else
echo " Niveau inconnu : $BUILD_TYPE"
fi
echo ""
echo "Détails de provenance :"
jq '{repository: .predicate.buildDefinition.externalParameters.workflow.repository, ref: .predicate.buildDefinition.externalParameters.workflow.ref, builder: .predicate.runDetails.builder.id}' /tmp/slsa-provenance.json
else
echo "Échec de la vérification SLSA"
exit 1
fi

Utilisation :

Fenêtre de terminal
chmod +x verify-slsa.sh
./verify-slsa.sh ghcr.io/stephrobert/test-sigstore:v1.0.2 github.com/stephrobert/test-sigstore v1.0.2

Pour communiquer le niveau SLSA atteint, ajoutez un badge dans votre README.md.

Le badge statique reflète le niveau visé — à mettre à jour si le projet progresse :

<!-- Badge SLSA Level 3 -->
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
<!-- Badge SLSA Level 2 -->
[![SLSA 2](https://slsa.dev/images/gh-badge-level2.svg)](https://slsa.dev)
<!-- Badge SLSA Level 1 -->
[![SLSA 1](https://slsa.dev/images/gh-badge-level1.svg)](https://slsa.dev)

Rendu :

SLSA 3

Ce badge se met à jour automatiquement avec le score réel du dépôt :

[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/owner/repo/badge)](https://scorecard.dev/viewer/?uri=github.com/owner/repo)

Ce badge affiche le score global (qui inclut SLSA parmi d'autres critères).

Si vous avez un workflow de vérification automatique, son badge de statut prouve que la vérification tourne réellement :

[![SLSA Verification](https://github.com/owner/repo/actions/workflows/verify-slsa.yml/badge.svg)](https://github.com/owner/repo/actions/workflows/verify-slsa.yml)

Voici comment ces badges et la section sécurité s'assemblent dans un README :

# Mon Application
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/owner/repo/badge)](https://scorecard.dev/viewer/?uri=github.com/owner/repo)
[![Build](https://github.com/owner/repo/actions/workflows/release.yml/badge.svg)](https://github.com/owner/repo/actions/workflows/release.yml)
## Supply Chain Security
Ce projet atteint **SLSA Level 3** pour garantir la traçabilité de la chaîne d'approvisionnement :
- Provenance SLSA générée pour chaque release
- Signatures Sigstore sur toutes les images
- Attestations vérifiables avec `gh attestation verify`
### Vérifier une release
```bash
# Vérifier l'image Docker
gh attestation verify oci://ghcr.io/owner/repo:v1.0.0 --owner owner
# Avec slsa-verifier
slsa-verifier verify-image ghcr.io/owner/repo:v1.0.0 \
--source-uri github.com/owner/repo \
--source-tag v1.0.0
```

Créez .github/workflows/verify-slsa.yml pour vérifier automatiquement vos releases, chaque nuit :

name: SLSA Verification
on:
schedule:
# Vérifier toutes les nuits
- cron: '0 2 * * *'
workflow_dispatch:
permissions: {}
jobs:
verify:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Installer slsa-verifier
run: |
curl -LO https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64
chmod +x slsa-verifier-linux-amd64
sudo mv slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier
- name: Récupérer la dernière release
id: latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LATEST_TAG=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q .tagName)
echo "tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
- name: Vérifier la provenance SLSA
env:
RELEASE_TAG: ${{ steps.latest.outputs.tag }}
run: |
slsa-verifier verify-image "ghcr.io/$GITHUB_REPOSITORY:$RELEASE_TAG" \
--source-uri "github.com/$GITHUB_REPOSITORY" \
--source-tag "$RELEASE_TAG" \
--print-provenance
- name: Rapporter le statut
if: always()
env:
JOB_STATUS: ${{ job.status }}
RELEASE_TAG: ${{ steps.latest.outputs.tag }}
run: |
if [ "$JOB_STATUS" = "success" ]; then
echo "Vérification SLSA réussie pour $RELEASE_TAG"
else
echo "Vérification SLSA échouée pour $RELEASE_TAG"
exit 1
fi

Ce workflow :

  1. S'exécute chaque nuit à 2h
  2. Récupère la dernière release
  3. Vérifie son attestation SLSA
  4. Échoue si la vérification rate

Vous pouvez ensuite afficher le badge de ce workflow dans votre README.

La vérification peut être imposée à l'admission : le cluster refuse tout pod dont l'image n'a pas d'attestation valide.

Kyverno applique une politique qui exige une attestation keyless GitHub sur les images :

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-attestation
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
matchImageReferences:
- glob: "ghcr.io/owner/*"
attestors:
- name: github-keyless
cosign:
keyless:
identities:
- issuer: "https://token.actions.githubusercontent.com"
subjectRegExp: "^https://github.com/owner/"
validations:
- expression: "true"
message: "Image must have a valid SLSA provenance attestation."

Le Policy Controller de Sigstore propose une ClusterImagePolicy au rôle équivalent :

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: github-attestation
spec:
images:
- glob: "ghcr.io/owner/**"
authorities:
- keyless:
identities:
- issuer: "https://token.actions.githubusercontent.com"
subjectRegExp: "https://github.com/owner/.*"

Trois échecs reviennent souvent — chacun a une cause précise et une parade.

Error: no attestations found for digest sha256:abc123

Causes possibles :

  1. L'artefact n'a pas d'attestation générée
  2. Le digest ne correspond pas
  3. Le repository n'a pas activé les attestations
Error: verification failed: signature mismatch

Causes possibles :

  1. L'artefact a été modifié après la signature
  2. Le mauvais fichier est vérifié
  3. L'attestation vient d'un autre artefact
Error: certificate identity mismatch

Cause : le workflow qui a signé ne correspond pas aux critères attendus.

Solution : vérifier --signer-workflow ou --certificate-identity-regexp.

Avant de valider une vérification, parcourez cette liste — chaque point ferme une voie de contournement :

  • Vérifier que le digest SHA256 correspond à l'artefact
  • Vérifier le repository source (--owner, --repo)
  • Vérifier le workflow source si critique (--signer-workflow)
  • Vérifier la version/tag attendu
  • Automatiser la vérification dans le pipeline de déploiement
  • Une attestation non vérifiée ne protège de rien : la vérification est l'étape qui ferme la boucle.
  • gh attestation verify est le moyen le plus direct ; cosign et slsa-verifier couvrent les besoins SLSA avancés.
  • En CI/CD, vérifier avant de déployer : aucun artefact non validé ne doit atteindre la production.
  • L'option --print-provenance révèle le buildType, donc le niveau SLSA réellement atteint.
  • Dans Kubernetes, Kyverno ou le Policy Controller Sigstore imposent la vérification à l'admission.

Pour la référence complète, consultez la documentation de gh attestation verify et le dépôt slsa-verifier.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn