From 50e0e9e258c8c1b7db124a894d61404a9b6b267f Mon Sep 17 00:00:00 2001 From: george-claude Date: Thu, 8 Oct 2026 13:20:09 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20build-push=20construit?= =?UTF-8?q?=20et=20scanne=20sur=20toute=20r=C3=A9f=C3=A9rence,=20PR=20comp?= =?UTF-8?q?rise=20;=20il=20ne=20publie=20que=20sur=20develop=20et=20les=20?= =?UTF-8?q?tags=20(raggaroth-factory/RF=5FCICD#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demande de Benjamin, 2026-10-08 : « il faut que le scan se fasse au build pour empêcher le build ». Jusqu'ici, le scan ne tournait que dans la publication, après la fusion : une PR n'était jamais scannée, et une dépendance vulnérable passait la revue. - Toute référence : construction + scan bloquant. Seules develop et vX.Y.Z publient ; une PR, une autre branche ou main s'arrêtent après le scan, sous un tag local « controle- » jamais poussé. - Identifiants du registre facultatifs : lus et exigés seulement si la référence publie. Une PR ne se connecte pas au registre. - REGISTRY_AUTH_FILE n'est exporté que pour publier : pointé sur un fichier absent, il faisait échouer buildah jusque dans l'export local vers le scan (constaté au banc, « credential file is not accessible »). - Sortie « publie » ; « tags » vide quand rien n'est publié. Banc, 2026-10-08, les deux étapes extraites du YAML et enchaînées : A. PR, sans identifiants, saine → code 0, registre vide B. PR, sans identifiants, vulnérable → code 1, image refusée C. develop, identifiants → :dev et : publiés D. develop, sans identifiants → code 1, erreur explicite E. main → code 0, rien publié Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VyxLNGHojdXeRMQgWFgzRV --- README.md | 46 +++++++++++++------- build-push/action.yml | 97 ++++++++++++++++++++++++++++++------------- 2 files changed, 97 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 7af54e3..fdf6269 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,41 @@ entrées des actions, depuis les secrets du dépôt appelant. ## `build-push` -Construit une image avec **buildah**, sans démon ni socket, et la publie dans -le registre de Gitea sous les tags de sa filière. **Ne déploie rien.** +Construit une image avec **buildah**, sans démon ni socket, la passe au **scan +de vulnérabilités bloquant**, puis — sur `develop` ou un tag `vX.Y.Z` +seulement — la publie dans le registre de Gitea. **Ne déploie rien.** + +**L'image est construite et scannée sur toute référence, PR comprise** : une +image vulnérable fait échouer le pipeline **avant** la fusion, pas après. Le +job qui l'appelle tourne donc sur chaque événement, juste après les tests : ```yaml -- uses: https://gitea.raggaroth-factory.fr/public/RF_ACTIONS/build-push@main - with: - image: gitea.raggaroth-factory.fr/raggaroth-factory/ - dockerfile: Dockerfile - context: . - registry_user: ${{ secrets.REGISTRY_USER }} - registry_password: ${{ secrets.REGISTRY_PASSWORD }} + image: + needs: tests + runs-on: docker + steps: + - uses: actions/checkout@ # vX.Y.Z + - uses: https://gitea.raggaroth-factory.fr/public/RF_ACTIONS/build-push@v1.0.0 + with: + image: gitea.raggaroth-factory.fr/raggaroth-factory/ + dockerfile: Dockerfile + context: . + registry_user: ${{ secrets.REGISTRY_USER }} + registry_password: ${{ secrets.REGISTRY_PASSWORD }} ``` -| Déclencheur | Tags publiés | -|---|---| -| poussée sur `develop` | `:dev` et `:` | -| tag `vX.Y.Z` (ou `vX.Y.Z-rc.1`) | `:vX.Y.Z`, orthographié comme le tag git, et rien d'autre | -| toute autre référence | **échec**, sans rien construire | +| Référence | Construction + scan | Publication | +|---|---|---| +| poussée sur `develop` | oui | `:dev` et `:` | +| tag `vX.Y.Z` (ou `vX.Y.Z-rc.1`) | oui | `:vX.Y.Z`, orthographié comme le tag git, et rien d'autre | +| PR, autre branche, `main` | oui | **rien** | -Sorties : `tags` (les tags publiés) et `digest` (l'empreinte `sha256:…` de -l'image publiée). +Les identifiants du registre ne sont lus **que** si la référence publie : une PR +ne se connecte pas au registre. Ils sont exigés dans ce cas, et leur absence est +une erreur explicite. + +Sorties : `publie` (`true` / `false`), `tags` (les tags publiés, vide sinon) et +`digest` (l'empreinte `sha256:…` de l'image publiée). **Scan de vulnérabilités bloquant.** Entre la construction et la publication, **Trivy** examine l'image. Une CVE **HIGH ou CRITICAL pour laquelle un diff --git a/build-push/action.yml b/build-push/action.yml index 8442851..a518682 100644 --- a/build-push/action.yml +++ b/build-push/action.yml @@ -1,21 +1,23 @@ -name: 'Build & push' +name: 'Build, scan & push' description: >- - Construit une image avec buildah, sans démon ni socket, et la publie dans le - registre de Gitea sous les tags de sa filière (ADR 0002 et 0003 de RF_CICD). Ne - déploie rien : la filière dev tire l'image, la production se déploie sur - ordre. Les tests et le lint du projet tournent AVANT cette étape, dans le - workflow appelant ; l'action ne filtre rien elle-même. + Construit une image avec buildah, sans démon ni socket, la passe au scan de + vulnérabilités BLOQUANT, et — sur develop ou un tag vX.Y.Z seulement — la + publie dans le registre de Gitea (ADR 0002 et 0003 de RF_CICD). Ne déploie + rien : la filière dev tire l'image, la production se déploie sur ordre. -# Tags publiés, selon ce qui a déclenché le workflow : +# Sur TOUTE référence — PR comprise — l'image est construite et scannée : une +# CVE HIGH ou CRITICAL corrigeable fait échouer le job, avant toute revue de +# fusion (RF_CICD#7, demande de Benjamin du 2026-10-08 : « le scan doit se faire +# au build pour empêcher le build »). Ensuite seulement, selon la référence : # -# poussée sur develop → :dev et : -# tag vX.Y.Z → :vX.Y.Z, orthographié comme le tag git, et rien d'autre -# toute autre référence → échec, sans rien construire +# poussée sur develop → publie :dev et : +# tag vX.Y.Z → publie :vX.Y.Z, orthographié comme le tag git +# toute autre référence → ne publie RIEN : construction et scan seulement # -# Le refus des autres références est voulu : une fusion sur main ne publie -# rien (ADR 0002), et une branche de fonctionnalité qui appellerait l'action -# par erreur ne doit pas pouvoir écraser :dev. Mieux vaut un job rouge qui dit -# pourquoi qu'une image publiée sous un nom que personne n'a choisi. +# Une PR, une branche de fonctionnalité, une poussée sur main (ADR 0002) ne +# publient donc jamais : elles ne peuvent ni écraser :dev, ni produire une image +# sous un nom que personne n'a choisi. Et elles n'ont pas besoin des +# identifiants du registre — l'action ne les demande que pour publier. # # Il n'y a volontairement aucun tag flottant de production (pas de :prod, pas # de :latest), et rien n'est élagué. @@ -38,16 +40,23 @@ inputs: required: false default: gitea.raggaroth-factory.fr registry_user: - description: Compte qui publie dans le registre. - required: true + description: Compte qui publie dans le registre. Exigé seulement quand la référence publie. + required: false + default: '' registry_password: - description: Jeton du compte qui publie. Par un secret Gitea Actions, jamais en clair. - required: true + description: >- + Jeton du compte qui publie. Par un secret Gitea Actions, jamais en clair. + Exigé seulement quand la référence publie. + required: false + default: '' outputs: tags: - description: Les tags publiés, séparés par des espaces. - value: ${{ steps.tags.outputs.tags }} + description: Les tags publiés, séparés par des espaces (vide si rien n'est publié). + value: ${{ steps.publish.outputs.tags }} + publie: + description: « true » si l'image a été publiée, « false » si elle a seulement été construite et scannée. + value: ${{ steps.tags.outputs.publier }} digest: description: L'empreinte (sha256:…) de l'image publiée — c'est elle que la filière dev compare. value: ${{ steps.publish.outputs.digest }} @@ -77,6 +86,7 @@ runs: # GITHUB_REF et non GITHUB_REF_NAME : seul le premier distingue une # branche « v1 » d'un tag « v1 ». + PUBLIER=true case "$GITHUB_REF" in refs/heads/develop) TAGS="dev ${GITHUB_SHA:0:12}" ;; @@ -91,13 +101,19 @@ runs: fi TAGS="$VERSION" ;; *) - echo "::error::« $GITHUB_REF » ne publie rien : seules une poussée sur develop et un tag vX.Y.Z publient (ADR 0002)" - exit 1 ;; + # Un tag LOCAL, jamais poussé : il ne sert qu'à nommer l'image le + # temps de la scanner. + TAGS="controle-${GITHUB_SHA:0:12}"; PUBLIER=false ;; esac echo "tags=$TAGS" >> "$GITHUB_OUTPUT" - echo "Tags : $TAGS" + echo "publier=$PUBLIER" >> "$GITHUB_OUTPUT" + if [ "$PUBLIER" = true ]; then + echo "« $GITHUB_REF » : construction, scan, puis publication de : $TAGS" + else + echo "« $GITHUB_REF » : construction et scan seulement — cette référence ne publie rien (ADR 0002)" + fi - - name: Construire et publier + - name: Construire, scanner, publier id: publish shell: bash env: @@ -106,6 +122,7 @@ runs: INPUT_CONTEXT: ${{ inputs.context }} INPUT_REGISTRY: ${{ inputs.registry }} INPUT_TAGS: ${{ steps.tags.outputs.tags }} + PUBLIER: ${{ steps.tags.outputs.publier }} REGISTRY_USER: ${{ inputs.registry_user }} REGISTRY_PASSWORD: ${{ inputs.registry_password }} run: | @@ -130,14 +147,27 @@ runs: # Fichier d'authentification explicite, dans le répertoire temporaire du # 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. - export REGISTRY_AUTH_FILE="${RUNNER_TEMP:-/tmp}/build-push-auth.json" + FICHIER_AUTH="${RUNNER_TEMP:-/tmp}/build-push-auth.json" IMAGE_OCI="${RUNNER_TEMP:-/tmp}/build-push-image-oci" - trap 'rm -rf -- "$REGISTRY_AUTH_FILE" "$IMAGE_OCI"' EXIT + trap 'rm -rf -- "$FICHIER_AUTH" "$IMAGE_OCI"' EXIT + # Connexion au registre SEULEMENT si la référence publie : une PR n'a + # pas à manipuler le jeton de publication, ni même à le recevoir. # printf est une primitive du shell : le jeton ne passe dans l'argv # d'aucun processus, donc pas dans /proc//cmdline. - printf '%s' "$REGISTRY_PASSWORD" \ - | b login --username "$REGISTRY_USER" --password-stdin "$INPUT_REGISTRY" + # REGISTRY_AUTH_FILE n'est exporté QUE si l'on publie : pointé sur un + # fichier inexistant, il fait échouer buildah jusque dans l'export local + # de l'image vers le scan (constaté le 2026-10-08, « credential file is + # not accessible »). + if [ "$PUBLIER" = true ]; then + export REGISTRY_AUTH_FILE="$FICHIER_AUTH" + if [ -z "$REGISTRY_USER" ] || [ -z "$REGISTRY_PASSWORD" ]; then + echo "::error::cette référence publie, mais registry_user / registry_password sont vides — secrets REGISTRY_USER / REGISTRY_PASSWORD du dépôt ?" + exit 1 + fi + printf '%s' "$REGISTRY_PASSWORD" \ + | b login --username "$REGISTRY_USER" --password-stdin "$INPUT_REGISTRY" + fi TAG_ARGS=() for TAG in $INPUT_TAGS; do TAG_ARGS+=(--tag "$INPUT_IMAGE:$TAG"); done @@ -199,10 +229,16 @@ runs: 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." + echo "::error::CVE HIGH ou CRITICAL corrigeable dans l'image : elle est REFUSÉE, et rien n'est publié. Corriger (monter l'empreinte de la base, ou la version du 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" + echo "Scan : aucune CVE HIGH ou CRITICAL corrigeable — image acceptée" + + if [ "$PUBLIER" != true ]; then + echo "tags=" >> "$GITHUB_OUTPUT" + echo "Construction et scan réussis — « $GITHUB_REF » ne publie rien." + exit 0 + fi DIGEST_FILE="${RUNNER_TEMP:-/tmp}/build-push-digest" for TAG in $INPUT_TAGS; do @@ -210,5 +246,6 @@ runs: echo "Publié : $INPUT_IMAGE:$TAG@$(cat "$DIGEST_FILE")" done echo "digest=$(cat "$DIGEST_FILE")" >> "$GITHUB_OUTPUT" + echo "tags=$INPUT_TAGS" >> "$GITHUB_OUTPUT" b logout "$INPUT_REGISTRY" || true