CI/CD : Supply Chain Security avec cosign, Trivy et SBOM

par

dans

Introduction

La sécurité de la chaîne d'approvisionnement logicielle (supply chain security) est devenue un enjeu critique depuis les attaques SolarWinds et Codecov. Pour un homelab qui produit et déploie des images Docker hardened FROM scratch, garantir l'intégrité et la provenance de chaque artefact est tout aussi important que dans un environnement enterprise. Cet article détaille le pipeline CI/CD complet qui construit, signe, scanne et atteste automatiquement les 7 images hardened du homelab.

Architecture du pipeline

Chaque image dispose de trois workflows automatisés sur GitHub Actions (source de vérité), avec un miroir GitLab CI pour le déploiement local sur le homelab :

┌─────────────────────────────────────────────────────────────────┐
│                    GitHub Actions Pipeline                       │
│                                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌───────────────┐   │
│  │ Lint     │→ │ Build    │→ │  Sign    │→ │ Scan + Attest │   │
│  │hadolint  │  │multi-arch│  │ cosign   │  │ Trivy + SLSA  │   │
│  │shellcheck│  │amd64+arm │  │ keyless  │  │               │   │
│  └──────────┘  └──────────┘  └──────────┘  └───────────────┘   │
│       │                                           │             │
│       ▼                                           ▼             │
│  ┌──────────┐                              ┌───────────────┐   │
│  │ Version  │  (daily cron)                │  Security     │   │
│  │  Watch   │──────────────────────────────│   Audit       │   │
│  │ upstream │                              │  (weekly)     │   │
│  └──────────┘                              └───────────────┘   │
│       │                                           │             │
│       ▼                                           ▼             │
│  Auto-rebuild + GitHub Release              GitHub Issue        │
│  si version obsolète                        si vulnérabilité    │
└─────────────────────────────────────────────────────────────────┘

Workflow 1 : Build and Push

Le workflow principal se déclenche sur chaque push sur la branche main et sur les tags de version. Il comprend les étapes suivantes :

Lint statique

  • Hadolint : analyse le Dockerfile pour détecter les anti-patterns (usage de latest, commandes inutiles, problèmes de cache)
  • ShellCheck : valide les scripts shell embarqués (entrypoints, healthchecks) pour éviter les erreurs POSIX et les failles d'injection

Build multi-plateforme

Chaque image est construite simultanément pour amd64 (serveurs x86) et arm64 (Raspberry Pi, Apple Silicon) via Docker Buildx et QEMU :

- name: Build and push
  uses: docker/build-push-action@v6
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: |
      ghcr.io/${{ github.repository }}:${{ env.VERSION }}
      docker.io/jbsky/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
    sbom: true
    provenance: mode=max
    cache-from: type=gha
    cache-to: type=gha,mode=max

Signature cosign keyless

Immédiatement après le push, l'image est signée via cosign en mode keyless (OIDC). Aucune clé privée n'est stockée — la signature utilise le token OIDC de GitHub Actions, vérifié par Sigstore Fulcio (CA éphémère) et enregistré dans le Rekor transparency log :

- name: Sign image with cosign
  env:
    COSIGN_EXPERIMENTAL: "true"
  run: |
    cosign sign --yes \
      ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

L'avantage du keyless : pas de gestion de clés, pas de rotation, pas de risque de compromission. L'identité du signataire est le workflow GitHub Actions lui-même, prouvable via le certificat Fulcio.

Scan Trivy

Trivy scanne l'image fraîchement construite pour détecter les CVE connues dans les packages OS et les dépendances applicatives. Le scan est configuré en mode strict :

  • Sévérités : CRITICAL et HIGH uniquement (les images FROM scratch n'ont typiquement aucune vulnérabilité Medium/Low)
  • En cas de CVE CRITICAL : le workflow échoue et bloque le déploiement
  • Les résultats sont uploadés au format SARIF vers GitHub Security tab

Attestation SLSA

L'attestation SLSA (Supply-chain Levels for Software Artifacts) niveau 3 est générée via l'action officielle :

- name: Attest build provenance
  uses: actions/attest-build-provenance@v4
  with:
    subject-name: ghcr.io/${{ github.repository }}
    subject-digest: ${{ steps.build.outputs.digest }}
    push-to-registry: true

Cette attestation prouve cryptographiquement que l'image a été construite par ce workflow spécifique, depuis ce commit spécifique, sur l'infrastructure GitHub Actions.

Workflow 2 : Version Watch

Un cron quotidien vérifie si les composants upstream ont publié de nouvelles versions :

  • Vérifie les versions de nginx, ModSecurity, OWASP CRS, PHP, Squid, ClamAV, Suricata, BIND9
  • Compare avec les versions encodées dans les labels OCI de l'image actuelle : versions=nginx=1.30.3 modsec=3.0.14 crs=4.13.0
  • Si une mise à jour est disponible : déclenche automatiquement un rebuild complet
  • Crée un GitHub Release avec changelog détaillant les changements de version

Ce mécanisme garantit que les images ne restent jamais plus de 24h en retard sur les correctifs de sécurité upstream.

Workflow 3 : Security Audit

Chaque semaine, un audit de sécurité approfondi est exécuté :

  • Trivy : scan complet avec base de données de vulnérabilités mise à jour
  • Grype : second scanner (Anchore) en cross-check pour éviter les faux négatifs
  • Comparaison des résultats des deux scanners
  • En cas de divergence ou de nouvelle vulnérabilité : création automatique d'une GitHub Issue avec les détails et la sévérité

Stratégie dual-registry

Chaque image est poussée simultanément vers deux registries :

  • ghcr.io (GitHub Container Registry) : registry principal, héberge aussi les signatures cosign et attestations SLSA
  • Docker Hub : mirror pour la compatibilité et la découvrabilité publique

Les signatures cosign sont stockées exclusivement sur ghcr.io (même pour les images Docker Hub), car ghcr.io supporte nativement les tags OCI de signature (sha256-*.sig).

SBOM (Software Bill of Materials)

Chaque build génère automatiquement un SBOM au format SPDX via BuildKit (--sbom=true). Ce document liste exhaustivement :

  • Tous les packages installés et leurs versions exactes
  • Les licences de chaque composant
  • Les dépendances transitives
  • Les checksums de chaque fichier

Le SBOM est attaché à l'image OCI comme attestation, consultable via cosign download sbom.

Build Provenance

Le flag --provenance=mode=max de BuildKit génère une provenance complète incluant :

  • Le Dockerfile source exact (hash)
  • Les build arguments utilisés
  • Les images de base avec leurs digests
  • Les timestamps de chaque layer
  • L'environnement de build (runner, OS, architecture)

Version tracking via OCI labels

Chaque image embarque un label OCI org.opencontainers.image.description contenant les versions des composants internes. Ce label est parsé par le workflow version-watch pour détecter les mises à jour :

LABEL org.opencontainers.image.description="versions=nginx=1.30.3 modsec=3.0.14 crs=4.13.0"

Ce mécanisme permet un suivi précis des versions sans dépendre d'un fichier externe ou d'une base de données.

Cache GitHub Actions

Le cache GHA est critique pour les builds longs. ModSecurity compile depuis les sources (~20 minutes sans cache). Le cache BuildKit (type=gha,mode=max) conserve toutes les layers intermédiaires entre les builds. Un rebuild après un changement mineur (mise à jour CRS uniquement) ne prend plus que 2-3 minutes au lieu de 20.

Builds proxy-aware

Certains environnements de build nécessitent un proxy pour accéder aux repositories de packages. Le pipeline utilise les BuildKit secrets pour injecter le certificat CA du proxy sans le persister dans l'image finale :

RUN --mount=type=secret,id=ca-cert,target=/usr/local/share/ca-certificates/proxy.crt \
    update-ca-certificates && \
    apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.21/main \
    build-base cmake git

Le secret est monté uniquement pendant l'exécution de la commande RUN et n'apparaît dans aucune layer de l'image résultante.

Vérification de la signature

N'importe qui peut vérifier l'authenticité d'une image signée :

cosign verify \
  --certificate-identity-regexp "https://github.com/jbsky/.*/.github/workflows/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/jbsky/nginx-waf-hardened:1.30.2

Cette commande vérifie que l'image a bien été signée par un workflow GitHub Actions du namespace jbsky, via l'émetteur OIDC officiel de GitHub.

Conclusion

La supply chain security n'est plus réservée aux grandes entreprises. Avec cosign keyless, Trivy, SLSA et les SBOM intégrés à BuildKit, un homelab peut atteindre un niveau de confiance équivalent aux standards industriels. Le coût marginal est quasi nul — GitHub Actions fournit gratuitement les minutes de CI pour les projets open-source, et Sigstore est un service public. L'investissement principal est la conception initiale des workflows, qui une fois en place, fonctionnent de manière entièrement autonome : rebuild automatique sur mise à jour upstream, alerte sur vulnérabilité, et traçabilité cryptographique complète de chaque artefact déployé.


Liens

Articles connexes


Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.