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.
PR Watch = Rôle d'observateur
Le workflow "cd-prod" devait :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
- name: Install crane
id: install
run: |
ci-scripts/install-crane.sh
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 │ │ │ │ │
│ │ │ │ │ │ │
└───────────┴────────────────┴──────────┴──────────┴───────────────────┴───────────────┘
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.
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
FROM gcr.io/distroless/java17-debian13:nonroot@sha256:81d09cac6ec47f6a13c61a941557f95079213320f3ddbf9d353de9317669aab5
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
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}"
Voir le script en entier dans le repository Github
name: Install crane
id: install
run: |
chmod +x ci-scripts/install-crane.sh
ci-scripts/install-crane.sh
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
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)
git diff --cached --quiet && {
echo "No changes detected"
echo "skip_pr=true" >> $GITHUB_OUTPUT
exit 0
}
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.
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.
- name: Scan Docker image (Trivy - conditional)
run: |
if [ "${{ steps.digest.outputs.digest_changed }}" = "true" ]; then
echo "[INFO] New distroless → strict scan"
//..
else
//..
fi
- 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
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].
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
- 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 \
//...
- 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
PR Watch → détecte develop/staging → valide main/prod → release
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é.
Build flashcards:staging → Trivy Scan Docker Image (strict) → healthcheck
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.