Maximize Page
Tech & DevOps HubEspace Tech & DevOps: Explorez le monde du Dev, du Cloud et des outils DevOps à travers nos articles et discussions Explore the world of development, the cloud and DevOps tools

Automatisation complète de la PR-watch-Distroless

Date de l'article:20-05-2026
Docker CI/CD Github-Actions UPDATE
Détection automatique des nouveaux digests Distroless et mise à jour sécurisée du Dockerfile via un workflow PR-watch entièrement automatisé. Chaque nouvelle image est ensuite validée dans le pipeline CI/CD avant promotion vers la production.

Cet article documente l'automatisation complète du workflow PR-watch-distroless — passage d'un simple observateur à un véritable point d'entrée de la chaîne CI/CD sécurité. Il fait suite à la résolution d'un incident DevSecOps lié à Trivy, mais peut se lire indépendamment.

Contexte Précédent
Voir le détail de l'analyse et son fix
Des vulnérabilités ont été détectées dans l'image Docker (distroless). Cette situation à été fixée, temporairement à l'aide de:
  • Ajout d'un script de détection, comparant le digest utilisé en CI, avec un tag "latest"
  • Ajout d'un workflow pr-watch-distroless pour vérifier les mises à jour de l'image
    • Condition: Envoye une PR vers[ main] si le digest change.
    • Un Cron est défini pour vérifier le digest tous les jours
    • Test de l'image dans le workflow Staging
  • Ajout du fichier .trivyignore pour ignorer les vulnérabilités présentes
  • Ajout des étapes nécessaires dans les workflows [staging] et [main]
Implication & contrainte de ce système:

PR Watch = Rôle d'observateur

Le workflow "cd-prod" devait :
détecter, décider, scanner différemment et mettre à jour , ce qui impliquait une répétition des étapes de scan et le Changement manuel du Dockerfile
Contexte "entre-deux" et Automatisation
2 Trop de vulnérabilités détectées dans l'image Debian:12
      J'ai donc pris la décision de:
  • Changer l'image de "Debian12" vers un "debian13:nonroot" a entraîné:
    • Le changement de l'image dans les scripts
    • La création du nouveau fichier .digest basé sur la nouvelle image
    • Conséquences directes:
      • Plus d'alertes dans le scan Trivy = Nettoyage des vulnérabilités dans le fichier .trivygnore
Avec l'automation du workflow, "PR-watch-distroless" deviendra le point d'entrée de la chaîne de validation sécurité en cas de nouveau digest detecté

1. Externalisation du script d'installation de Crane

Dans l'étape précédente: la PR-watch appelle Crane, et Crane était installé directement dans les workflows

Pour l'automatisé et ne plus l'installer manuellement dans tous les jobs des workflows, ce qui peut-être contraignant en cas de mise à jour. Nous allons donc l'externaliser et, comme nous avons une version déjà, nous pouvons donc la fixer

ci-scripts/install-crane.sh
#!/usr/bin/env bash
set -euo pipefail

CRANE_VERSION="v0.21.5"
echo "[INFO] Installing crane ${CRANE_VERSION}..."

curl -fsSL \
  "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz" \
  | tar -xz crane

sudo mv crane /usr/local/bin/
echo "[INFO] crane installed: "
crane version

Ajout du script dans la [pr-watch-distroless]
- name: Install crane
  id: install
  run: |
    ci-scripts/install-crane.sh
Suppression de l'étape dans les workflows
Les précédentes étapes d'installation de Crane peuvent être supprimées définitivement des branches [staging] et [main]
Install crane

2. Modification de l'image Docker
1. Changement de la version de l'image
Modifier la ligne dans le Dockerfile:
FROM gcr.io/distroless/java17-debian13:nonroot Dans un premier temps, pour valider qu'elle passe bien le flow et qu'il n'y a plus de vulnérabilités dans l'image Docker.
2. Création/suppression du fichier .digest
→ Suppression de l'ancien qui ne nous sert plus (debian12) et ajouter le nouveau: ".distroless-java17-debian13.digest", adapté a la nouvelle version de l'image

Résultat de la modification de l'image [staging]
Report Summary
┌──────────────────────────────────┬────────┬─────────────────┬─────────┐
│              Target              │  Type  │ Vulnerabilities │ Secrets │
├──────────────────────────────────┼────────┼─────────────────┼─────────┤
│ flashcards:staging (debian 13.5) │ debian │        2        │    -    │
├──────────────────────────────────┼────────┼─────────────────┼─────────┤
│ app/app.jar                      │  jar   │        0        │    -    │
└──────────────────────────────────┴────────┴─────────────────┴─────────┘

flashcards:staging (debian 13.5)
================================
Total: 2 (HIGH: 2, CRITICAL: 0)

┌───────────┬────────────────┬──────────┬──────────┬───────────────────┬───────────────┐
│  Library  │ Vulnerability  │ Severity │  Status  │ Installed Version │ Fixed Version │
├───────────┼────────────────┼──────────┼──────────┼───────────────────┼───────────────┤
│ libexpat1 │ CVE-2026-25210 │ HIGH     │ affected │ 2.7.1-2           │               │
│           │                │          │          │                   │               │
│           ├────────────────┤          │          │                   ├───────────────┤
│           │ CVE-2026-45186 │          │          │                   │               │
│           │                │          │          │                   │               │
└───────────┴────────────────┴──────────┴──────────┴───────────────────┴───────────────┘
2 vulnérabilités ont été trouvée, mais pas de fix de disponible.

Contrairement à l'image "debian12" où il indiquait qu'un correctif existait, mais qu'il n'était pas encore intégré dans l'image analysée.

En passant à Debian 13, on bénéficie d'un nettoyage massif des anciennes vulnérabilités grâce à la mise à jour globale des bibliothèques système de l'image
Dans la CI, le comportement diffère volontairement entre les branches:
  • [staging] exécute un scan strict affichant toutes les vulnérabilités HIGH/CRITICAL, même sans correctif disponible.
  • [main] utilise --ignore-unfixed, ce qui ignore les vulnérabilités sans patch officiel disponible afin d'éviter de bloquer inutilement la release.
À partir de là, Docker pull la dernière image du tag, indépendamment du digest détecté.
Donc, quand le Docker build, il n'utilise pas encore le digest. Pour ce faire, il nous faut "figer" le digest dans le Dockerfile.

3. Pin de l'image Docker

Pour une sécurité maximale et des builds 100% reproductibles, l'idéal est donc, de figer l'image par son empreinte unique (digest SHA256) au lieu du simple tag textuel.
Dès que Google publierait une nouvelle image Debian 13, et que la PR-watch l'aura detectée, le .digest et le Dockerfile seront mis à jour et prêt à être testés dans le CI

Modifier l'image dans le Dockerfile:
Remplacer: FROM gcr.io/distroless/java17-debian13:nonroot
par:
FROM gcr.io/distroless/java17-debian13:nonroot@sha256:81d09cac6ec47f6a13c61a941557f95079213320f3ddbf9d353de9317669aab5
Le SHA à été obtenu de la même manière décrite que dans l'article précédent (Docker), le script utilise également cette approche.
On peux aussi voir les tags/Sha, depuis la registry Google

3. Automatisation de la "pr-watch-distroless" pour gérer le digest
EVENT La PR à détecté un nouveau Digest

C'est le moment de tester réellement le système et l'automatiser de manière à ce qu'il suive la stratégie gitlab Flow sur 3 branches

Résultat du PR-watch-distroless
 Run chmod +x ci-scripts/check-docker-image-latest.sh
[INFO] Checking crane availability...
[INFO] Fetching current digest...
[INFO] Current:  sha256:81d09cac6ec47f6a13c61a941557f95079213320f3ddbf9d353de9317669aab5
[INFO] Previous: sha256:b0e67f7fa5649297e655e37b2cd67471d7d38c2f8617790e9d8e8eab78ed6bcb
[INFO] Update detected
La stratégie
> "pr-watch-distroless" detecte un nouveau digest,
  1. Il modifie le Dockerfile avec le nouveau SHA
  2. Il modifie le .digest avec le nouveau SHA
  3. il envoie une PR à la branche [develop]
      à partir là, la CI suit le flux normal

1. Modification du fichier "ci-scripts/check-docker-image-latest.sh"
ci-scripts/check-docker-image-latest

Dans la première version, le système surveillait simplement l'évolution du tag distroless afin de détecter les rebuilds publiés upstream.
Désormais, le workflow surveille explicitement l'image java17-debian13:nonroot et pin automatiquement le digest SHA256 correspondant.

IMAGE="${1:-gcr.io/distroless/java17-debian13:nonroot}"
STATE_FILE="${2:-ci-scripts/.distroless-java17-debian13.digest}"
Ces variables de script sont transmis depuis la pr-watch-distroless.yml, en tant qu'arguments.
Dans le script; la variable $IMAGE est utilisée pour obtenir le CURRENT_DIGEST=$(crane digest "$IMAGE") et le comparer ensuite au digest déjà stocké dans le fichier .digest (STATE_FILE)

2. Modification de la ".github/workflows/pr-watch-distroless.yml" (extrait)

Voir le script en entier dans le repository Github

Dans cette nouvelle version, nous allons changer toute la logique et attribuer + de responsabilité à ce workflow:
1. Pour respecter notre flow Git, au lieu d'appeller --base main \ on appellera --base develop \
2. Nous installons Crane pour rendre le workflow indépendant (CRON):
 name: Install crane
  id: install
  run: |
    chmod +x ci-scripts/install-crane.sh
    ci-scripts/install-crane.sh
3. au lieu de laisser [main] mettre à jour le .digest nous le ferons directement depuis la Detection
4. Comme il à changé le digest, et pour nous permettre de tester la nouvelle image dans staging, il mettra à jour le tag pinné dans le Dockerfile
BRANCH="chore/distroless-update-$(date +%s)"
  git checkout -b $BRANCH
  DIGEST="${{ steps.check.outputs.digest_value }}"

  echo "$DIGEST" > ci-scripts/.distroless-java17-debian13.digest
  sed -i "s|FROM gcr.io/distroless/java17-debian13:nonroot.*|FROM gcr.io/distroless/java17-debian13:nonroot@$DIGEST|" Dockerfile
  git add \
    ci-scripts/.distroless-java17-debian13.digest \
    Dockerfile
5. Nous en profitons pour sécuriser la branche aussi:
Au lieu d'appeller --search "distroless" \ on appellera --search "in:title chore: distroless image updated" \ qui corresponds au titre de la pr, mentionné dans le :
 gh pr create \
  --base develop \
  --head "$BRANCH" \
  --title "chore: distroless image updated" \ 

Ce qui évitera une confusion éventuelle au cas ou d'autres branche porterait ce même nom (distroless)

> Et le commit aussi:
 git diff --cached --quiet && {
    echo "No changes detected"
    echo "skip_pr=true" >> $GITHUB_OUTPUT
    exit 0
  }

Modification dans les worflows

On avait déja nettoyé l'installation de Crane, maintenant nous supprimons les conditions qui ne sont plus nécessaires car le digest est automatique ajouté. Quand le build Docker s'effectue, il prends l'image du Dockerfile directement. Plus besoin d'effectuer d'autres installations ou vérifications dans les workflows.


1. Modification du workflow [staging]

Le digest étant désormais géré en amont par le workflow PR-watch-distroless, les workflows staging et production n'ont plus besoin de détecter dynamiquement les changements d'image. Ils se concentrent uniquement sur la validation et l'exécution des contrôles CI/CD.

Suppression des étapes devenues inutiles:
Check distroless digest
Debug digest
staging sert désormais uniquement de validation runtime/intégration.
Supprimer ou remplacer également cette étape:
- name: Scan Docker image (Trivy - conditional)
  run: |
    if [ "${{ steps.digest.outputs.digest_changed }}" = "true" ]; then
      echo "[INFO] New distroless → strict scan"
      //..
    else
     //..
    fi
par:
- name: Trivy Scan Docker Image (strict)
  run: |
    echo "[INFO] New distroless → strict scan"
    trivy image flashcards:staging \
      --severity HIGH,CRITICAL \
      --ignorefile /dev/null \
      --exit-code 0 \
      --format table
Résultat

On allège et simplifie les workflows
Le scan devient strict, sans ignore, à chaque build.
[staging] devient un vrai environnement de validation de la sécurité, avant la promotion vers [main].


2. Modification du workflow [main]

Parce que les responsabilités ont été déplacées en amont vers le workflow "pr-watch-distroless" afin que les nouvelles images soient validées dans le flux Git normal, Le workflow n'à plus besoin de vérifier le digest, ni de le mettre jour

Suppression des étapes devenues inutiles

Check distroless digest
Debug FULL outputs
Update digest file
On supprime ou remplace cette étape:
- name: Trivy Scan Docker Image (new distroless)
  if: steps.digest.outputs.digest_changed == 'true'
  run: |
    trivy image $IMAGE_NAME:$IMAGE_TAG \
      --ignorefile /dev/null \
      //..

- name: Trivy Scan Docker Image (stable distroless)
  if: steps.digest.outputs.digest_changed == 'false'
  run: |
      //...
      --ignorefile .trivyignore.yaml \
      //...
Pour la remplacer par une simple étape:

- name: Trivy Scan Docker Image
  run: |
    trivy image $IMAGE_NAME:$IMAGE_TAG \
      --severity HIGH,CRITICAL \
      --exit-code 1 \
      --ignore-unfixed \
      --ignorefile .trivyignore.yaml \
      --timeout 10m \
      --cache-dir ~/.cache/trivy \
      --format table \
      --no-progress
Le fichier .trivyignore.yaml reste conservé dans le pipeline afin de permettre de futures exceptions contrôlées si nécessaire, même si les vulnérabilités historiquement ignorées ont désormais été nettoyées.
À présent, la responsabilité de la décision sécurité a été déplacée en amont du pipeline de production. Donc:
PR Watch → détecte  develop/staging → valide  main/prod → release

Résultat et conclusion

Cette évolution marque une montée en maturité du pipeline CI/CD et de la stratégie DevSecOps du projet.
Le workflow "pr-watch-distroless" ne joue désormais plus uniquement un rôle d'observateur, mais devient un véritable point d'entrée de la chaîne de validation sécurité.

L'ensemble permet :
  • des builds reproductibles,
  • une meilleure maîtrise de la supply chain Docker,
  • une réduction des étapes conditionnelles dans les workflows,
  • et une séparation plus claire entre détection, validation et release.

Le workflow "ci-staging" sert désormais de validation runtime/intégration.
Job "functional-tests":
Build flashcards:staging → Trivy Scan Docker Image (strict) → healthcheck

Le workflow "cd-prod" re-devient enfin ce qu'un pipeline de production doit être:
build → tests → scan → smoke test → release → push registry
Distroless Registry
        │
        ▼
PR-watch-distroless
        │
        ├── update Dockerfile
        ├── update .digest
        └── create PR → develop
                        │
                        ▼
                staging validation
                        │
                        ▼
                   production

Ce pattern — un workflow dédié à la détection, qui alimente un flux Git standard — est réutilisable dans tout projet nécessitant une surveillance d'images Docker upstream, quelle que soit la registry utilisée.


Laissez-moi un commentaire

En postant un commentaire anonyme, vous adhérez automatiquement aux conditions d'utilisation du site.