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
#!/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 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
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: |
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" \
git diff --cached --quiet && {
echo "No changes detected"
echo "skip_pr=true" >> $GITHUB_OUTPUT
exit 0
}
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
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
- 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.