Introduction : Pourquoi la signature des artefacts est essentielle en CI/CD
Les pipelines modernes de livraison logicielle sont remarquablement efficaces pour construire et livrer du code rapidement. Mais la rapidité sans confiance est un risque. Entre le moment où le code source est commité et celui où une image container s’exécute en production, il existe un intervalle — un intervalle où la falsification, la substitution ou la corruption silencieuse peuvent survenir sans que personne ne s’en aperçoive.
Il ne s’agit pas d’une préoccupation théorique. L’attaque SolarWinds en 2020 a démontré comment des adversaires pouvaient injecter du code malveillant dans un processus de build, produisant des artefacts signés mais compromis qui se sont propagés à des milliers d’organisations. La brèche Codecov en 2021 a montré comment un script CI falsifié pouvait exfiltrer des secrets de chaque dépôt l’utilisant. Dans les deux cas, la supply chain — l’infrastructure entre le code et le déploiement — constituait la surface d’attaque.
La signature des artefacts répond à une pièce critique de ce puzzle : l’authenticité et l’intégrité. En signant cryptographiquement une image container, vous créez un lien vérifiable entre l’image et l’identité (personne, équipe ou système CI) qui l’a produite. Les consommateurs de cette image peuvent alors vérifier la signature avant de l’exécuter, s’assurant qu’elle n’a pas été modifiée depuis sa construction.
Pendant des années, la signature était impraticable dans la plupart des organisations. La gestion des clés GPG était fastidieuse, la distribution des clés était fragile, et l’outillage nécessitait une expertise cryptographique approfondie. Sigstore a changé la donne. Il a introduit une suite d’outils open-source qui rendent la signature et la vérification accessibles, automatisées et — point crucial — sans clé (keyless).
Ce guide parcourt l’écosystème Sigstore, vous montre comment signer et vérifier des images container avec Cosign, intégrer la signature dans les pipelines CI/CD, et attacher des attestations et des SBOMs. À la fin, vous aurez une compréhension pratique de la façon d’intégrer la signature des artefacts comme partie standard de votre processus de livraison.
Qu’est-ce que Sigstore ?
Sigstore est un projet open-source sous la Linux Foundation qui fournit des outils gratuits, transparents et faciles à utiliser pour signer, vérifier et protéger les artefacts logiciels. Il a été créé pour résoudre un problème spécifique : rendre la signature cryptographique pratique pour l’écosystème open-source et au-delà.
L’écosystème Sigstore se compose de trois composants fondamentaux :
Cosign
Cosign est l’outil côté client pour signer et vérifier les images container et autres artefacts OCI. C’est ce avec quoi les développeurs et les pipelines CI/CD interagissent directement. Cosign prend en charge à la fois la signature traditionnelle par paire de clés et le nouveau flux de signature keyless.
Fulcio
Fulcio est une autorité de certification (CA) gratuite qui émet des certificats à courte durée de vie basés sur une identité OpenID Connect (OIDC). Lorsque vous utilisez la signature keyless, Fulcio vérifie votre identité via un fournisseur OIDC (tel que Google, GitHub ou Microsoft) et émet un certificat qui lie votre identité à une clé de signature. Le certificat n’est valide que quelques minutes — suffisamment longtemps pour signer un artefact, mais assez court pour que la compromission de clé ne soit pas un risque persistant.
Rekor
Rekor est un log de transparence — un registre immuable, en ajout seul, qui enregistre les événements de signature. Chaque fois qu’un artefact est signé, un enregistrement est ajouté à Rekor. Cela fournit une piste publique et auditable de qui a signé quoi et quand. C’est conceptuellement similaire aux logs de Certificate Transparency utilisés dans l’écosystème TLS.
En quoi la signature keyless diffère de GPG
La signature traditionnelle basée sur GPG vous oblige à générer une paire de clés à longue durée de vie, protéger la clé privée, distribuer la clé publique, et gérer la rotation et la révocation des clés. C’est opérationnellement lourd et sujet aux erreurs, c’est pourquoi la plupart des projets n’ont jamais adopté la signature.
La signature keyless de Sigstore élimine cette charge :
- Pas de clés à longue durée de vie — Les clés de signature sont éphémères. Elles n’existent que pendant la durée de l’opération de signature.
- Confiance basée sur l’identité — Au lieu de faire confiance à une clé, vous faites confiance à une identité (par exemple, un workflow GitHub Actions, une adresse e-mail spécifique). Fulcio lie l’identité à la clé éphémère via un certificat à courte durée de vie.
- Transparence par défaut — Chaque événement de signature est enregistré dans Rekor, créant un enregistrement auditable sans nécessiter l’exploitation de votre propre infrastructure.
- Pas de problème de distribution de clés — Les vérificateurs n’ont pas besoin d’obtenir une clé publique par un canal séparé. Ils vérifient par rapport à l’identité et au log de transparence.
Signer des images container avec Cosign
Installation de Cosign
Cosign est distribué sous forme de binaire unique. Vous pouvez l’installer sur la plupart des plateformes :
# macOS (Homebrew)
brew install cosign
# Linux (Go install)
go install github.com/sigstore/cosign/v2/cmd/cosign@latest
# Linux (binary release)
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
# Verify installation
cosign version
Signature avec une paire de clés
L’approche la plus simple consiste à générer une paire de clés et à l’utiliser pour signer les images. C’est utile lorsque vous souhaitez un contrôle total sur la gestion des clés ou lorsque la signature keyless n’est pas disponible dans votre environnement.
Étape 1 : Générer une paire de clés
cosign generate-key-pair
Cela crée deux fichiers : cosign.key (clé privée, protégée par mot de passe) et cosign.pub (clé publique). Stockez la clé privée en lieu sûr — dans un gestionnaire de secrets, un HSM ou un coffre-fort chiffré.
Étape 2 : Signer une image container
# Signer une image par son digest (toujours préférer le digest au tag)
cosign sign --key cosign.key ghcr.io/myorg/myapp@sha256:abc123...
Cosign vous demandera le mot de passe de la clé privée, générera une signature et la poussera vers le même registre OCI à côté de l’image. La signature est stockée comme un artefact OCI séparé, tagué selon une convention qui la lie au digest de l’image.
Important : Signez toujours par digest, pas par tag. Les tags sont mutables — quelqu’un pourrait déplacer un tag pour pointer vers une image différente après la signature. Les digests sont adressés par contenu et immuables.
Signature keyless avec identité OIDC
La signature keyless est l’approche recommandée pour les pipelines CI/CD. Elle supprime entièrement la nécessité de gérer des clés de signature.
# Signature keyless (interactive — ouvre le navigateur pour la connexion OIDC)
cosign sign ghcr.io/myorg/myapp@sha256:abc123...
# Signature keyless (non interactive, pour CI/CD)
# Le flag --yes ignore l'invite de confirmation
cosign sign --yes ghcr.io/myorg/myapp@sha256:abc123...
Dans un environnement CI/CD comme GitHub Actions, Cosign détecte automatiquement le jeton OIDC ambiant fourni par la plateforme. Aucune interaction navigateur n’est nécessaire.
Ce qui se passe en coulisses
Lorsque vous exécutez cosign sign --yes en mode keyless, la séquence suivante se produit :
- Génération de clé éphémère — Cosign génère une paire de clés temporaire en mémoire.
- Authentification OIDC — Cosign obtient un jeton d’identité OIDC depuis l’environnement (par exemple, le fournisseur OIDC de GitHub Actions) ou vous invite à vous authentifier via un navigateur.
- Émission du certificat — Cosign envoie la clé publique et le jeton OIDC à Fulcio. Fulcio vérifie le jeton, extrait l’identité (e-mail, URI du workflow, etc.) et émet un certificat X.509 à courte durée de vie liant l’identité à la clé publique.
- Signature — Cosign signe le digest de l’image en utilisant la clé privée éphémère.
- Journalisation de transparence — La signature, le certificat et le digest de l’image sont enregistrés dans Rekor. Cette entrée est horodatée et immuable.
- Téléversement de la signature — La signature est poussée vers le registre OCI en tant qu’artefact compagnon.
- Destruction de la clé — La clé privée éphémère est supprimée. Elle n’est jamais stockée nulle part.
Le résultat est une image signée avec une chaîne vérifiable : le log Rekor prouve que la signature a été créée à un moment précis par une identité spécifique, et le certificat Fulcio prouve que l’identité a été authentifiée au moment de la signature.
Vérification des signatures
La signature n’est utile que si les consommateurs vérifient. Cosign fournit des commandes de vérification simples pour les scénarios basés sur des clés et keyless.
Vérification avec une clé publique
Si l’image a été signée avec une paire de clés, vérifiez en utilisant la clé publique :
cosign verify --key cosign.pub ghcr.io/myorg/myapp@sha256:abc123...
Cosign récupère la signature depuis le registre OCI, la vérifie par rapport à la clé publique et affiche le résultat. Si la signature est valide, il affiche la charge utile de signature. Sinon, il se termine avec une erreur.
Vérification keyless
Pour les images signées en mode keyless, la vérification est basée sur l’identité plutôt que sur une clé. Vous spécifiez qui vous vous attendez à avoir signé l’image :
# Vérifier qu'un workflow GitHub Actions spécifique a signé l'image
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
Cette commande vérifie que :
- L’image possède une signature valide.
- Le certificat de signature a été émis par Fulcio.
- L’identité dans le certificat correspond au
--certificate-identityspécifié. - L’émetteur OIDC correspond au
--certificate-oidc-issuer. - L’événement de signature est enregistré dans le log de transparence Rekor.
Vous pouvez également utiliser la correspondance par expression régulière pour des politiques plus flexibles :
cosign verify \
--certificate-identity-regexp "https://github.com/myorg/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
Application de la vérification dans les contrôleurs d’admission
La vérification manuelle est utile pour le débogage, mais les environnements de production nécessitent une application automatisée. Les contrôleurs d’admission Kubernetes peuvent rejeter toute image qui ne possède pas de signature valide.
Kyverno est un moteur de politiques populaire avec une prise en charge intégrée de la vérification Cosign :
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/myorg/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
Le Sigstore Policy Controller (anciennement Cosign Policy Webhook) fournit une fonctionnalité similaire avec une approche native Sigstore :
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "ghcr.io/myorg/**"
authorities:
- keyless:
identities:
- issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"
ctlog:
url: https://rekor.sigstore.dev
Avec l’une ou l’autre approche, tout pod qui référence une image non signée (ou incorrectement signée) de votre registre sera rejeté au moment de l’admission.
Intégration de la signature dans les pipelines CI/CD
GitHub Actions avec signature keyless
GitHub Actions dispose d’un support OIDC natif, ce qui en fait l’environnement idéal pour la signature keyless. Voici un workflow complet qui construit, pousse et signe une image container :
name: Build and Sign Container Image
on:
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write # Required for keyless signing
jobs:
build-sign:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Sign the image
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${DIGEST}
Points clés :
- La permission
id-token: writeactive le fournisseur OIDC de GitHub Actions, que Cosign utilise pour la signature keyless. - L’image est signée par digest, pas par tag, en utilisant la sortie de l’étape de build.
- Aucun secret ni clé n’est nécessaire — le jeton OIDC de GitHub est l’identité.
Exemple GitLab CI
GitLab CI prend également en charge les jetons OIDC pour la signature keyless. L’approche est similaire mais utilise le mécanisme de jeton d’identité de GitLab :
stages:
- build
- sign
variables:
IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE .
- docker push $IMAGE
- echo "DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE | cut -d@ -f2)" >> build.env
artifacts:
reports:
dotenv: build.env
sign:
stage: sign
image: alpine:3.19
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
before_script:
- apk add --no-cache cosign
script:
- cosign sign --yes ${CI_REGISTRY_IMAGE}@${DIGEST}
Le bloc id_tokens indique à GitLab de générer un jeton OIDC avec l’audience sigstore. Cosign récupère automatiquement le jeton depuis la variable d’environnement SIGSTORE_ID_TOKEN.
Stockage des signatures dans les registres OCI
Par défaut, Cosign stocke les signatures dans le même registre OCI que l’image signée. La signature est poussée en tant que tag séparé suivant la convention sha256-<digest>.sig. Cela signifie :
- Aucune infrastructure supplémentaire n’est nécessaire pour le stockage des signatures.
- Les signatures accompagnent l’image lors de la mise en miroir ou de la réplication.
- La plupart des registres majeurs (GHCR, Docker Hub, ECR, GCR, ACR, Harbor) prennent en charge les artefacts OCI et fonctionnent avec Cosign nativement.
Si vous devez stocker les signatures dans un registre différent (par exemple, un stockage dédié aux signatures), vous pouvez utiliser la variable d’environnement COSIGN_REPOSITORY :
export COSIGN_REPOSITORY=ghcr.io/myorg/signatures
cosign sign --yes ghcr.io/myorg/myapp@sha256:abc123...
Attestations et rattachement de SBOM
Les signatures prouvent qui a construit une image. Les attestations vont plus loin — elles prouvent comment elle a été construite et ce qu’elle contient. Cosign prend en charge le rattachement de métadonnées structurées aux images sous forme d’attestations in-toto.
Rattachement de la provenance SLSA avec cosign attest
SLSA (Supply-chain Levels for Software Artifacts) décrit le processus de build : quelle source a été utilisée, quel constructeur a été exécuté, quelles étapes ont été réalisées. Vous pouvez rattacher une attestation de provenance SLSA à une image :
# Créer une déclaration de provenance (exemple simplifié)
cat > provenance.json <<'EOF'
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "ghcr.io/myorg/myapp",
"digest": {
"sha256": "abc123..."
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/myorg/myapp/.github/workflows/release.yml",
"resolvedDependencies": [
{
"uri": "git+https://github.com/myorg/myapp@refs/heads/main",
"digest": {
"sha1": "def456..."
}
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/actions/runner"
}
}
}
}
EOF
# Attester l'image avec la déclaration de provenance (keyless)
cosign attest --yes \
--predicate provenance.json \
--type slsaprovenance \
ghcr.io/myorg/myapp@sha256:abc123...
En pratique, vous utiliseriez un générateur SLSA (tel que slsa-github-generator) pour produire automatiquement la provenance plutôt que de la rédiger manuellement.
Rattachement des SBOMs
Un Software Bill of Materials (SBOM) liste chaque composant à l'intérieur de votre image container. Cosign peut rattacher un SBOM à une image afin que les consommateurs puissent inspecter son contenu :
# Générer un SBOM avec Syft
syft ghcr.io/myorg/myapp@sha256:abc123... -o spdx-json > sbom.spdx.json
# Rattacher le SBOM en tant qu'attestation (approche recommandée)
cosign attest --yes \
--predicate sbom.spdx.json \
--type spdxjson \
ghcr.io/myorg/myapp@sha256:abc123...
Alternativement, vous pouvez utiliser l'ancienne commande cosign attach sbom, bien que l'approche par attestation soit préférée car elle est signée et vérifiable :
# Ancienne approche (rattaché mais non signé)
cosign attach sbom --sbom sbom.spdx.json \
ghcr.io/myorg/myapp@sha256:abc123...
Vérification des attestations
Les consommateurs peuvent vérifier les attestations exactement comme les signatures. La commande cosign verify-attestation vérifie à la fois la signature de l'attestation et l'identité du signataire :
# Vérifier l'attestation de provenance SLSA
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
# Vérifier l'attestation SBOM
cosign verify-attestation \
--type spdxjson \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp@sha256:abc123...
Vous pouvez également utiliser des moteurs de politiques comme Kyverno pour vérifier les attestations au moment de l'admission, garantissant que seules les images avec une provenance ou des SBOMs valides sont déployées sur vos clusters.
Considérations de sécurité et compromis
La signature des artefacts est un outil puissant, mais il est important de comprendre ce contre quoi elle protège et ce contre quoi elle ne protège pas.
Ce contre quoi la signature protège
- Falsification après le build — Si quelqu'un modifie une image après qu'elle a été signée, la signature ne sera plus valide. Cela détecte les compromissions de registre, les attaques de type man-in-the-middle et la corruption accidentelle.
- Usurpation d'identité — Avec la signature keyless, l'identité du signataire est cryptographiquement liée à la signature. Un attaquant ne peut pas forger une signature prétendant provenir de votre pipeline CI sans compromettre le fournisseur OIDC.
- Répudiation — Le log de transparence Rekor fournit un enregistrement infalsifiable des événements de signature. Les signataires ne peuvent pas nier avoir signé un artefact.
Ce contre quoi la signature ne protège PAS
- Code source malveillant — La signature prouve que l'image a été construite par un pipeline autorisé. Elle ne prouve pas que le code source est exempt de vulnérabilités ou de portes dérobées. Un compte développeur compromis qui pousse du code malveillant produira une image légitimement signée.
- Environnements de build compromis — Si le runner CI lui-même est compromis (comme dans le scénario SolarWinds), l'attaquant peut produire des artefacts signés. La signature prouve l'identité, pas l'intégrité de l'environnement de build.
- Vulnérabilités dans les dépendances — Une image signée peut toujours contenir des CVE connues. La signature et l'analyse de vulnérabilités sont complémentaires, pas substituables.
- Contournement de politique — La signature ne fonctionne que si la vérification est appliquée. Si votre contrôleur d'admission a des exceptions, ou si les équipes peuvent déployer sans passer par celui-ci, la signature n'offre aucune protection pour ces chemins.
Hypothèses du modèle de confiance
La signature keyless repose sur plusieurs hypothèses de confiance :
- Confiance dans le fournisseur OIDC — Vous faites confiance à GitHub, GitLab ou Google pour authentifier correctement les identités. Une compromission du fournisseur OIDC sape l'ensemble du modèle.
- Confiance dans Fulcio — Vous faites confiance à l'instance Fulcio de Sigstore pour vérifier correctement les jetons OIDC et émettre des certificats uniquement aux identités authentifiées.
- Confiance dans Rekor — Vous faites confiance au log de transparence pour être en ajout seul et infalsifiable. L'instance publique Rekor de Sigstore est opérée par la communauté ; pour les environnements à haute assurance, vous pouvez souhaiter exploiter la vôtre.
Pour les organisations ayant des exigences de conformité strictes, l'exploitation d'une infrastructure Sigstore privée (CA Fulcio privée, log Rekor privé) offre un contrôle total sur la chaîne de confiance.
Considérations de gestion des clés
Si vous choisissez la signature par clé au lieu de la signature keyless :
- Stockez les clés privées dans un KMS (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault). Cosign dispose d'un support KMS natif :
cosign sign --key awskms:///arn:aws:kms:... - Effectuez la rotation des clés selon un calendrier régulier et disposez d'un plan de révocation.
- Évitez de stocker les clés privées comme secrets CI/CD — elles sont généralement journalisées, mises en cache et répliquées de manières qui augmentent l'exposition.
- Utilisez des clés séparées pour les différents environnements (staging vs. production) afin de limiter le rayon d'impact.
Conclusion
La signature d'images container avec Sigstore et Cosign est l'une des mesures les plus impactantes que vous puissiez prendre pour sécuriser votre supply chain logicielle. Ce n'est plus difficile, cela ne nécessite plus une expertise cryptographique approfondie, et cela ne demande plus une infrastructure complexe de gestion des clés. Avec la signature keyless, un pipeline CI/CD peut signer chaque image qu'il produit sans aucun secret à gérer et avec une auditabilité complète via le log de transparence Rekor.
Mais la signature est un élément constitutif, pas une solution miracle. Elle répond à la question « cette image a-t-elle été produite par un processus autorisé ? » Elle ne répond pas à « cette image est-elle sûre à exécuter ? » Une stratégie complète de sécurité de la supply chain combine la signature avec l'analyse de vulnérabilités, la génération de SBOM, la provenance SLSA, l'application de politiques à l'admission, la surveillance de sécurité en runtime et des contrôles d'accès rigoureux sur les dépôts source et l'infrastructure de build.
Commencez par ajouter cosign sign --yes à votre pipeline CI/CD. Puis ajoutez la vérification dans votre contrôleur d'admission. Ensuite, superposez les attestations et les SBOMs. Chaque étape réduit l'écart entre la construction du logiciel et la confiance qu'on lui accorde.
Les outils sont matures, l'écosystème est en croissance, et le coût de ne pas signer devient de plus en plus difficile à justifier. La question n'est plus de savoir s'il faut signer vos artefacts — c'est à quelle vitesse vous pouvez en faire la norme.