TP12 : Software Supply Chain (Harbor, SBOM, Cosign, provenance, SLSA)
Scanner son code ne suffit plus : des attaques comme SolarWinds, Codecov ou la porte dérobée de xz-utils ont compromis la chaîne de fabrication elle-même (dépendance, outil de build, registre). Ce TP met en place les briques qui permettent de savoir ce que contient une image (SBOM), de prouver qui l’a construite et comment (signature, provenance), et de refuser ce qui n’est pas prouvé. Le tout autour d’un registre privé Harbor.
Fichiers de départ :
workflow-starter.zip
1. Objectifs pédagogiques
À l’issue du TP, vous serez capable de :
- installer Harbor et y configurer projets, RBAC, robot accounts, scan automatique et rétention ;
- générer un SBOM (SPDX, CycloneDX) avec Syft et l’analyser avec Grype ;
- signer et vérifier une image avec Cosign, avec une paire de clés puis en mode keyless (OIDC) ;
- produire une attestation de provenance dans GitHub Actions et la vérifier ;
- situer un pipeline sur les niveaux SLSA et expliquer le rôle d’in-toto.
Les images de cette page sont des schémas explicatifs, pas des captures d’écran.
2. Environnement du TP
- Une VM dédiée Ubuntu Server 26.04 pour Harbor (≥ 2 vCPU, 4 Go de RAM, 40 Go de disque), par exemple créée avec Vagrant (TP5). Harbor occupe le port 80.
- La VM de lab (Docker) comme poste client. Les deux VM se joignent par IP privée.
- Dans le fichier
/etc/hostsdes deux VM :<IP-harbor> harbor.lab.local. - L’image
contact-apides TP précédents (ou toute image que vous avez construite).
| Terme | Signification |
|---|---|
| SBOM | Software Bill of Materials : inventaire des composants d’un logiciel (paquets, versions, licences). |
| Digest | Empreinte sha256:… du manifest d’une image. Immuable, contrairement à un tag. |
| Signature | Preuve cryptographique qu’un digest a été approuvé par le détenteur d’une clé ou d’une identité. |
| Attestation | Déclaration signée sur un artefact (provenance, SBOM, résultat de scan), au format in-toto. |
| Provenance | Attestation qui décrit où, quand, comment et à partir de quelles sources un artefact a été construit. |
| SLSA | Supply-chain Levels for Software Artifacts : niveaux d’exigence croissants pour la fabrication d’un artefact. |
3. Partie 1 : Installer Harbor
-
Sur la VM Harbor, installer Docker et Compose, puis télécharger l’installeur en ligne depuis les releases Harbor :
wget https://github.com/goharbor/harbor/releases/download/<version>/harbor-online-installer-<version>.tgz tar xzf harbor-online-installer-*.tgz && cd harbor cp harbor.yml.tmpl harbor.yml -
Dans
harbor.yml:hostname: harbor.lab.local, commenter tout le blochttps:(pas de certificat dans le lab), changerharbor_admin_password. -
Installer avec le scanner Trivy :
sudo ./install.sh --with-trivy docker compose ps -
Sur la VM client, autoriser ce registre HTTP dans
/etc/docker/daemon.json, puis redémarrer Docker :{ "insecure-registries": ["harbor.lab.local"] }En production, un registre est toujours servi en HTTPS. Le mode HTTP n’est acceptable que dans un lab isolé.
4. Partie 2 : Projets, RBAC, scan et rétention
-
Dans l’interface
http://harbor.lab.local(admin) :- créer un projet privé
devsecops; - créer un utilisateur
dev1avec le rôle Developer sur ce projet, et un utilisateuraudit1avec le rôle Guest ; - créer un robot account
ci-pushlimité au projetdevsecops(push + pull uniquement).
- créer un projet privé
-
Pousser une image avec le robot account :
docker login harbor.lab.local -u 'robot$devsecops+ci-push' docker tag contact-api:latest harbor.lab.local/devsecops/contact-api:1.0.0 docker push harbor.lab.local/devsecops/contact-api:1.0.0 -
Vérifier le RBAC :
audit1peut tirer l’image mais pas la pousser (denied: requested access to the resource is denied). -
Projet → Configuration : activer Automatically scan images on push et Prevent vulnerable images from running (gravité
Critical). Pousser une image vulnérable (celle du TP10 avant correction) et constater que ledocker pullest refusé. -
Policy → Tag Retention : conserver les 5 derniers tags poussés, puis lancer un dry run après avoir poussé 7 tags. Policy → Tag Immutability : rendre immuables les tags
v*. -
Comparer GitHub Container Registry et Harbor (hébergement, RBAC, scan, réplication, coût, usage hors ligne).
5. Partie 3 : SBOM avec Syft, scan avec Grype
-
Générer un SBOM de l’image dans deux formats :
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/out" anchore/syft:latest \ contact-api:latest -o spdx-json=/out/contact-api.spdx.json -o cyclonedx-json=/out/contact-api.cdx.json -
Ouvrir le SBOM : combien de paquets ? Retrouver la version d’
expresset celle d’openssl. -
Analyser le SBOM (et non l’image) avec Grype :
docker run --rm -v "$PWD:/in" anchore/grype:latest sbom:/in/contact-api.spdx.jsonIntérêt : conserver le SBOM de chaque release permet de répondre en quelques secondes à la question « suis-je concerné par la nouvelle CVE X ? » sans reconstruire ni retélécharger les images.
6. Partie 4 : Signer et vérifier avec Cosign
-
Installer Cosign (binaire depuis les releases Sigstore) puis générer une paire de clés :
cosign generate-key-pair # crée cosign.key (privée, chiffrée) et cosign.pubcosign.keyne doit jamais être commitée (elle est dans le.gitignoredu dépôt). -
Récupérer le digest de l’image poussée, puis la signer :
DIGEST=$(docker buildx imagetools inspect harbor.lab.local/devsecops/contact-api:1.0.0 --format '{{json .Manifest.Digest}}' | tr -d '"') cosign sign --key cosign.key --allow-http-registry --allow-insecure-registry \ harbor.lab.local/devsecops/contact-api@$DIGEST -
Vérifier la signature, puis vérifier une image non signée (tag
1.0.1poussé sans signature) :cosign verify --key cosign.pub --allow-http-registry --allow-insecure-registry \ harbor.lab.local/devsecops/contact-api@$DIGEST -
Attacher le SBOM comme attestation signée, puis la vérifier :
cosign attest --key cosign.key --type spdxjson --predicate contact-api.spdx.json \ --allow-http-registry --allow-insecure-registry harbor.lab.local/devsecops/contact-api@$DIGEST cosign verify-attestation --key cosign.pub --type spdxjson \ --allow-http-registry --allow-insecure-registry harbor.lab.local/devsecops/contact-api@$DIGEST -
Dans Harbor, constater que l’image affiche une signature et des accessoires (attestation).
7. Partie 5 : Keyless et provenance dans GitHub Actions
Harbor n’est pas joignable depuis GitHub : cette partie publie dans GHCR (ghcr.io).
-
Copier
workflow-starter/release.ymldans.github/workflows/du dépôtcontact-apiet compléter lesTODO: SBOM (anchore/sbom-action), signature Cosign keyless, attestation de provenance (actions/attest-build-provenance), SBOM attaché à la release.En mode keyless, aucune clé n’est stockée : GitHub fournit un jeton OIDC qui prouve l’identité du workflow, Fulcio émet un certificat éphémère, et la signature est inscrite dans le journal public de transparence Rekor.
-
Créer un tag
v1.0.0et le pousser. Une fois le workflow terminé, vérifier depuis la VM :cosign verify ghcr.io/<utilisateur>/contact-api:v1.0.0 \ --certificate-identity-regexp 'https://github.com/<utilisateur>/contact-api/.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com gh attestation verify oci://ghcr.io/<utilisateur>/contact-api:v1.0.0 --owner <utilisateur> -
Que se passe-t-il si
--certificate-identity-regexpdésigne un autre dépôt ? Pourquoi est-ce important ?
8. Partie 6 : SLSA, in-toto et attaque simulée
- Lire la spécification SLSA v1.0 (Build track) et situer : (a) un build sur le poste du développeur, (b) votre pipeline Jenkins, (c) votre workflow GitHub Actions avec attestation.
| Niveau | Exigence principale | Atteint par |
|---|---|---|
| Build L1 | Provenance existante | |
| Build L2 | Provenance signée, générée par une plateforme hébergée | |
| Build L3 | Plateforme de build durcie, provenance infalsifiable par le projet |
- Expliquer en quelques lignes le rôle d’in-toto (layout, étapes, links) et le lien avec les attestations produites par Cosign et GitHub.
- Attaque simulée : un « attaquant » pousse une image
contact-api:1.0.0modifiée sous le même tag. Montrer quecosign verifysur le nouveau digest échoue, alors qu’un simpledocker pullne voit rien. - Écrire un script
verify-before-run.shqui refuse de lancer (docker run) une image dont la signature n’est pas valide.
Défi
- Configurer une réplication Harbor (Registries → New Endpoint Docker Hub, règle pull-based) pour mettre en cache une image publique.
- Ne plus garder la clé privée dans un fichier : importez-la dans le pipeline sous forme de credential Jenkins ou de secret GitHub (
COSIGN_PRIVATE_KEY,COSIGN_PASSWORD) et signez depuis la CI. La gestion centralisée des secrets sera approfondie au TP18.
9. Livrables
Captures à déposer dans captures/ :
| Fichier | Contenu attendu |
|---|---|
harbor-project.png |
Projet devsecops avec ses membres et le robot account |
harbor-rbac-denied.png |
Push refusé pour audit1 |
harbor-scan.png |
Rapport de scan Trivy dans Harbor et pull bloqué |
harbor-retention.png |
Dry run de la politique de rétention |
sbom-grype.png |
Extrait du SBOM et résultat de Grype |
cosign-verify.png |
Vérification réussie puis échec sur une image non signée |
keyless-provenance.png |
cosign verify keyless et gh attestation verify réussis |
attack-detected.png |
Image remplacée détectée par cosign verify |
Ainsi que release.yml, verify-before-run.sh, le tableau SLSA et la comparaison GHCR / Harbor. Jamais cosign.key.