Exercice 02 : Générer un SBOM et signer ses images Docker avec cosign
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Générer un SBOM en format SPDX et CycloneDX avec
syft - ✅ Signer une image Docker avec
cosignen mode keyless (OIDC) - ✅ Attacher le SBOM comme attestation cosign à l'image
- ✅ Vérifier la signature d'une image avant de la déployer
- ✅ Intégrer signature et SBOM dans un workflow GitHub Actions
Durée estimée : 40 minutes
Difficulté : ⭐⭐⭐☆☆ (Avancé)
Prérequis : Docker, compte GitHub avec repository, cosign et syft installables
📖 Contexte
La supply chain attack SolarWinds (2020) a montré qu'un attaquant peut compromettre le processus de build pour injecter du code malveillant dans une image officielle. Pour s'en protéger, il faut pouvoir prouver que l'image que vous déployez est bien celle qui a été buildée par votre CI, et pas une image modifiée.
SBOM (Software Bill of Materials) : inventaire complet de tous les composants d'une image, comme la liste d'ingrédients d'un produit alimentaire.
cosign : outil de signature cryptographique d'images de conteneurs. La signature est stockée dans le registre à côté de l'image.
📋 Énoncé
Buildez une image Docker, générez son SBOM, signez-la avec cosign, et vérifiez la chaîne de confiance.
🧭 Déroulement de l'exercice
Tâche 1 : Installer syft et cosign
# Installer syft (générateur de SBOM)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Installer cosign
COSIGN_VERSION=$(curl -s https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d'"' -f4)
curl -sSL "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" -o /tmp/cosign
chmod +x /tmp/cosign && sudo mv /tmp/cosign /usr/local/bin/cosign
# Vérifications
syft version
cosign versionTâche 2 : Créer et builder une image Docker
mkdir sbom-cosign-demo && cd sbom-cosign-demo
cat > Dockerfile << 'EOF'
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
USER app
EXPOSE 3000
CMD ["node", "server.js"]
EOF
cat > package.json << 'EOF'
{
"name": "sbom-demo",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0",
"helmet": "^7.1.0"
}
}
EOF
echo "require('express')().get('/',(r,s)=>s.send('ok')).listen(3000)" > server.js
npm install
# Builder l'image
docker build -t sbom-demo:latest .
docker tag sbom-demo:latest sbom-demo:1.0.0Tâche 3 : Générer le SBOM avec syft
# Générer un SBOM en format SPDX JSON (standard de l'industrie)
syft sbom-demo:latest -o spdx-json=sbom.spdx.json
# Générer aussi en CycloneDX (format utilisé par Dependency-Track)
syft sbom-demo:latest -o cyclonedx-json=sbom.cyclonedx.json
# Lire le SBOM : combien de composants ?
cat sbom.spdx.json | python3 -c "
import json, sys
sbom = json.load(sys.stdin)
packages = sbom.get('packages', [])
print(f'Total packages: {len(packages)}')
# Afficher les 5 premiers
for p in packages[:5]:
print(f' - {p[\"name\"]} {p.get(\"versionInfo\", \"\")}')
"Indice : Un SBOM complet liste TOUS les composants : packages npm, bibliothèques système Alpine, binaires. Pour
node:22-alpine, le SBOM peut contenir 200-400 composants. C'est cette liste exhaustive qui permet à Trivy ou Dependency-Track de détecter des CVE dans votre image.
Vérification : sbom.spdx.json existe et contient plus de 50 packages.
Tâche 4 : Signer l'image avec cosign (keyless)
Le mode keyless utilise votre identité OIDC (GitHub Actions, Google, etc.) comme preuve d'identité. La signature est enregistrée dans Rekor (journal public d'audit).
# Pousser l'image vers un registre (nécessaire pour la signature)
# Pour cet exercice, utilisons le registre GitHub
# Remplacez votre-username par votre GitHub username
docker tag sbom-demo:latest ghcr.io/votre-username/sbom-demo:latest
docker push ghcr.io/votre-username/sbom-demo:latest
# Signer l'image (keyless = utilise l'OIDC GitHub si dans Actions, sinon interactif)
cosign sign --yes ghcr.io/votre-username/sbom-demo:latest
# Attacher le SBOM comme attestation à l'image
cosign attest --yes \
--type spdxjson \
--predicate sbom.spdx.json \
ghcr.io/votre-username/sbom-demo:latestIndice : En mode keyless, cosign ouvre un navigateur pour l'authentification OIDC. Dans GitHub Actions, ce processus est automatique via
ACTIONS_ID_TOKEN_REQUEST_URL. La signature est stockée dans Rekor (https://rekor.sigstore.dev) - un journal public immuable.
Tâche 5 : Vérifier la signature et le SBOM
# Vérifier que l'image est bien signée
cosign verify \
--certificate-identity-regexp "https://github.com/votre-username/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/votre-username/sbom-demo:latest
# Vérifier et extraire l'attestation SBOM
cosign verify-attestation \
--type spdxjson \
--certificate-identity-regexp "https://github.com/votre-username/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/votre-username/sbom-demo:latest | jq '.payload | @base64d | fromjson | .predicate.packages | length'Vérification : La commande verify ne doit pas afficher d'erreur et doit retourner les informations de signature.
Tâche 6 : Intégrer dans GitHub Actions
mkdir -p .github/workflows
cat > .github/workflows/build-sign.yml << 'EOF'
name: Build, SBOM et Signature
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-sign:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Requis pour cosign keyless
steps:
- uses: actions/checkout@v4
- name: Installer cosign
uses: sigstore/cosign-installer@v3
- name: Installer syft
uses: anchore/sbom-action/download-syft@v0
- name: Login au registre GitHub
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build et push
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Générer le SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- name: Signer l'image (keyless)
run: |
cosign sign --yes \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
- name: Attacher le SBOM comme attestation
run: |
cosign attest --yes \
--type spdxjson \
--predicate sbom.spdx.json \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
EOF✅ Vérification du résultat
syftgénère un SBOM avec 50+ packages listéssbom.spdx.jsonetsbom.cyclonedx.jsonsont créés- L'image est poussée sur le registre
cosign signcomplète sans erreurcosign verifyconfirme la signature valide
💡 À retenir
La chaîne de confiance supply chain :
Code → Build CI → SBOM (quoi ?) → Signature cosign (qui ? quand ?)
↓
Rekor (journal public immuable)
↓
Déploiement → Vérification de signatureSi quelqu'un modifie l'image après sa signature, cosign verify échoue. Sans signature, vous déployez ce que vous pensez être votre image - pas ce qui a réellement été buildé.
✨ Solution Complète
# Générer SBOM
syft mon-image:latest -o spdx-json=sbom.json
# Signer et attacher SBOM
cosign sign --yes mon-registre.io/mon-image:latest
cosign attest --yes --type spdxjson --predicate sbom.json mon-registre.io/mon-image:latest
# Vérifier avant déploiement
cosign verify --certificate-identity-regexp "github.com/mon-org" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
mon-registre.io/mon-image:latest