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é.
Laisser un commentaire