Merge pull request '🔒️ build-push construit et scanne sur toute référence, PR comprise ; il ne publie que sur develop et les tags (RF_CICD#7)' (#5) from ci/7-scan-a-chaque-construction into develop
Reviewed-on: #5 Reviewed-by: bbaudouin <b.baudouin@pm.me>
This commit was merged in pull request #5.
This commit is contained in:
@@ -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/<nom-de-l-image>
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
registry_user: ${{ secrets.REGISTRY_USER }}
|
||||
registry_password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
image:
|
||||
needs: tests
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@<empreinte de commit> # 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/<nom-de-l-image>
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
registry_user: ${{ secrets.REGISTRY_USER }}
|
||||
registry_password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
```
|
||||
|
||||
| Déclencheur | Tags publiés |
|
||||
|---|---|
|
||||
| poussée sur `develop` | `:dev` et `:<sha court, 12 caractères>` |
|
||||
| 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 `:<sha court, 12 caractères>` |
|
||||
| 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
|
||||
|
||||
+67
-30
@@ -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 :<sha court, 12 caractères>
|
||||
# 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 :<sha court, 12 caractères>
|
||||
# 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/<pid>/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
|
||||
|
||||
Reference in New Issue
Block a user