Merge pull request '🔒️ build-push refuse de publier une image qui porte une CVE HIGH ou CRITICAL corrigeable (RF_CICD#7)' (#3) from ci/7-scan-cve-bloquant into develop
Reviewed-on: #3 Reviewed-by: bbaudouin <b.baudouin@pm.me>
This commit was merged in pull request #3.
This commit is contained in:
@@ -36,7 +36,31 @@ le registre de Gitea sous les tags de sa filière. **Ne déploie rien.**
|
|||||||
Sorties : `tags` (les tags publiés) et `digest` (l'empreinte `sha256:…` de
|
Sorties : `tags` (les tags publiés) et `digest` (l'empreinte `sha256:…` de
|
||||||
l'image publiée).
|
l'image publiée).
|
||||||
|
|
||||||
**Exigences** : l'image du conteneur de job doit porter `buildah`. L'action
|
**Scan de vulnérabilités bloquant.** Entre la construction et la publication,
|
||||||
|
**Trivy** examine l'image. Une CVE **HIGH ou CRITICAL pour laquelle un
|
||||||
|
correctif existe** fait échouer le job : rien n'est publié. Les autres — dont
|
||||||
|
les CVE sans correctif, contre lesquelles personne ne peut rien — sont
|
||||||
|
rapportées dans le journal sans bloquer. Le seuil n'est pas une entrée de
|
||||||
|
l'action : un dépôt ne peut pas l'abaisser pour lui seul.
|
||||||
|
|
||||||
|
Une CVE **acceptée sciemment** va dans `.trivyignore.yaml`, à la racine du
|
||||||
|
dépôt appelant. Chaque entrée porte une justification et une date
|
||||||
|
d'expiration ; sans l'une ou l'autre, l'action refuse le fichier. À
|
||||||
|
l'échéance, Trivy cesse d'appliquer l'exception et la CVE bloque de nouveau.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vulnerabilities:
|
||||||
|
- id: CVE-2026-93990
|
||||||
|
statement: libexpat de l'image nginx ; correctif attendu dans la prochaine base
|
||||||
|
expired_at: 2026-11-15
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plus souvent, la correction est une ligne dans le Dockerfile — mettre à
|
||||||
|
jour les paquets de la base (`apk upgrade --no-cache`, `apt-get upgrade`) —
|
||||||
|
plutôt qu'une exception.
|
||||||
|
|
||||||
|
**Exigences** : l'image du conteneur de job doit porter `buildah` **et
|
||||||
|
`trivy`**. L'action
|
||||||
construit avec `--storage-driver vfs --isolation chroot`, ce qui fonctionne
|
construit avec `--storage-driver vfs --isolation chroot`, ce qui fonctionne
|
||||||
dans un conteneur non privilégié, sans capacité ajoutée ni `/dev/fuse`. Les
|
dans un conteneur non privilégié, sans capacité ajoutée ni `/dev/fuse`. Les
|
||||||
`FROM` des Dockerfiles construits doivent être des **noms complets**
|
`FROM` des Dockerfiles construits doivent être des **noms complets**
|
||||||
|
|||||||
+58
-5
@@ -111,10 +111,12 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if ! command -v buildah >/dev/null; then
|
for OUTIL in buildah trivy; do
|
||||||
echo "::error::buildah absent de l'image de job : le runner doit exécuter ce job dans une image qui le porte (voir README de RF_ACTIONS)"
|
if ! command -v "$OUTIL" >/dev/null; then
|
||||||
exit 1
|
echo "::error::$OUTIL absent de l'image de job : le runner doit exécuter ce job dans une image qui le porte (voir README de RF_ACTIONS)"
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# « --storage-driver vfs » sur CHAQUE appel, et pas par la variable
|
# « --storage-driver vfs » sur CHAQUE appel, et pas par la variable
|
||||||
# STORAGE_DRIVER : constaté le 2026-10-07 sur buildah 1.33.7, avec la
|
# STORAGE_DRIVER : constaté le 2026-10-07 sur buildah 1.33.7, avec la
|
||||||
@@ -129,7 +131,8 @@ runs:
|
|||||||
# job, et effacé en sortie quoi qu'il arrive. Le conteneur est jeté à la
|
# job, et effacé en sortie quoi qu'il arrive. Le conteneur est jeté à la
|
||||||
# fin du job, mais un jeton n'a pas à lui survivre d'une étape.
|
# fin du job, mais un jeton n'a pas à lui survivre d'une étape.
|
||||||
export REGISTRY_AUTH_FILE="${RUNNER_TEMP:-/tmp}/build-push-auth.json"
|
export REGISTRY_AUTH_FILE="${RUNNER_TEMP:-/tmp}/build-push-auth.json"
|
||||||
trap 'rm -f "$REGISTRY_AUTH_FILE"' EXIT
|
IMAGE_OCI="${RUNNER_TEMP:-/tmp}/build-push-image-oci"
|
||||||
|
trap 'rm -rf -- "$REGISTRY_AUTH_FILE" "$IMAGE_OCI"' EXIT
|
||||||
|
|
||||||
# printf est une primitive du shell : le jeton ne passe dans l'argv
|
# printf est une primitive du shell : le jeton ne passe dans l'argv
|
||||||
# d'aucun processus, donc pas dans /proc/<pid>/cmdline.
|
# d'aucun processus, donc pas dans /proc/<pid>/cmdline.
|
||||||
@@ -151,6 +154,56 @@ runs:
|
|||||||
"${TAG_ARGS[@]}" \
|
"${TAG_ARGS[@]}" \
|
||||||
"$INPUT_CONTEXT"
|
"$INPUT_CONTEXT"
|
||||||
|
|
||||||
|
# ── Scan de vulnérabilités, BLOQUANT, avant toute publication ──────────
|
||||||
|
# Seuil décidé le 2026-10-07 (RF_CICD#7) : une CVE HIGH ou CRITICAL
|
||||||
|
# pour laquelle un correctif est publié fait échouer le job, comme un
|
||||||
|
# test rouge. Le reste — dont les CVE sans correctif, contre lesquelles
|
||||||
|
# personne ne peut rien — est rapporté sans bloquer. Le seuil n'est PAS
|
||||||
|
# une entrée de l'action : un dépôt ne peut pas l'abaisser pour lui seul.
|
||||||
|
# Une CVE acceptée sciemment va dans .trivyignore.yaml, à la racine du
|
||||||
|
# dépôt appelant, avec une justification et une date d'expiration.
|
||||||
|
#
|
||||||
|
# Trivy lit l'image construite sous forme de RÉPERTOIRE OCI : il n'y a
|
||||||
|
# pas de démon à interroger, et rien n'est publié avant son verdict.
|
||||||
|
# Un répertoire et non une archive : « --input » n'accepte que l'archive
|
||||||
|
# au format Docker (manifest.json) ou le répertoire OCI (index.json) —
|
||||||
|
# une archive OCI est refusée (constaté, trivy 0.75.0).
|
||||||
|
PREMIER_TAG=${INPUT_TAGS%% *}
|
||||||
|
b push "$INPUT_IMAGE:$PREMIER_TAG" "oci:$IMAGE_OCI"
|
||||||
|
|
||||||
|
IGNORE=()
|
||||||
|
FICHIER_IGNORE="${GITHUB_WORKSPACE:-.}/.trivyignore.yaml"
|
||||||
|
if [ -f "$FICHIER_IGNORE" ]; then
|
||||||
|
# Chaque exception porte un « statement » (pourquoi) et un
|
||||||
|
# « expired_at » (jusqu'à quand) : à l'échéance, Trivy cesse de
|
||||||
|
# l'appliquer et la CVE bloque de nouveau. Une exception sans date
|
||||||
|
# serait une CVE acceptée pour toujours, sans que personne l'ait dit.
|
||||||
|
if ! awk '
|
||||||
|
/^[[:space:]]*-[[:space:]]*id:/ { n++; id[n]=$0 }
|
||||||
|
/^[[:space:]]*statement:/ { s[n]=1 }
|
||||||
|
/^[[:space:]]*expired_at:/ { e[n]=1 }
|
||||||
|
END { ok=1; for (i=1;i<=n;i++) if (!s[i] || !e[i]) { print " incomplète :" id[i]; ok=0 } exit !ok }
|
||||||
|
' "$FICHIER_IGNORE"; then
|
||||||
|
echo "::error::.trivyignore.yaml : chaque exception doit porter « statement » et « expired_at »"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
IGNORE=(--ignorefile "$FICHIER_IGNORE")
|
||||||
|
echo "Exceptions lues dans .trivyignore.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rapport complet des HIGH/CRITICAL, corrigeables ou non — pour voir,
|
||||||
|
# pas pour juger. Puis le verdict, sur les seules corrigeables.
|
||||||
|
echo "::group::Vulnérabilités HIGH et CRITICAL, corrigeables ou non (rapport)"
|
||||||
|
trivy image --quiet --scanners vuln --severity HIGH,CRITICAL \
|
||||||
|
"${IGNORE[@]}" --format table --exit-code 0 --input "$IMAGE_OCI"
|
||||||
|
echo "::endgroup::"
|
||||||
|
if ! trivy image --quiet --skip-db-update --scanners vuln --severity HIGH,CRITICAL \
|
||||||
|
--ignore-unfixed "${IGNORE[@]}" --format table --exit-code 1 --input "$IMAGE_OCI"; then
|
||||||
|
echo "::error::CVE HIGH ou CRITICAL corrigeable dans l'image : rien n'est publié. Corriger (mettre à jour la base ou le paquet), ou l'accepter sciemment dans .trivyignore.yaml, avec justification et date d'expiration."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Scan : aucune CVE HIGH ou CRITICAL corrigeable — publication autorisée"
|
||||||
|
|
||||||
DIGEST_FILE="${RUNNER_TEMP:-/tmp}/build-push-digest"
|
DIGEST_FILE="${RUNNER_TEMP:-/tmp}/build-push-digest"
|
||||||
for TAG in $INPUT_TAGS; do
|
for TAG in $INPUT_TAGS; do
|
||||||
b push --digestfile "$DIGEST_FILE" "$INPUT_IMAGE:$TAG" "docker://$INPUT_IMAGE:$TAG"
|
b push --digestfile "$DIGEST_FILE" "$INPUT_IMAGE:$TAG" "docker://$INPUT_IMAGE:$TAG"
|
||||||
|
|||||||
Reference in New Issue
Block a user