
Le lab sécurité construit un dépôt
qui publie une image attestée sur GHCR. Il s'arrête là : rien ne dit qui
autorise la mise en production, ni comment revenir en arrière. Ce lab ajoute
la pièce manquante, le workflow deploy.yml, et met en évidence la propriété
qui fait toute la valeur des environnements : un job en attente d'approbation
n'a jamais vu les secrets de production.
Ce que vous allez construire
Section intitulée « Ce que vous allez construire »- Deux environnements,
stagingetproduction, avec des secrets distincts - Une approbation obligatoire et une restriction de branche sur la production
- Un workflow
deploy.ymlqui promeut un digest vérifié, sans rebuild - Un rollback qui rejoue la version précédente par le même chemin
Prérequis
Section intitulée « Prérequis »Ce lab suppose acquis le build vérifiable : vous avez un dépôt qui publie une image sur GHCR avec une attestation de provenance. Le dépôt de référence github.com/stephrobert/secure-python-pipeline en fournit la base ; forkez-le dans votre compte, ce lab écrit dans les réglages du dépôt et dans ses workflows.
Côté outillage, la CLI gh est authentifiée et Docker est disponible en
local. Côté GitHub, un point n'est pas négociable : les règles de protection
d'environnement exigent un dépôt public sur un plan Free, ou au minimum
GitHub Pro sur un dépôt privé. Laissez votre fork public, c'est
d'ailleurs ce qui rendra vos attestations vérifiables par un tiers.
Étape 1 : créer les deux environnements
Section intitulée « Étape 1 : créer les deux environnements »-
Ouvrir les réglages du fork, section Settings > Environments.
-
Créer
stagingavec New environment. Aucune règle pour l'instant. -
Créer
productionde la même façon. -
Ajouter un secret à chacun : Environment secrets > Add secret, nommé
DEPLOY_TOKENdans les deux cas, avec des valeurs différentes et reconnaissables, par exemplestaging-token-abcetprod-token-xyz.Ces valeurs factices sont volontaires : le lab va prouver quel secret chaque job reçoit, et à quel moment.
Vérification immédiate, les deux environnements apparaissent dans la liste, et chacun affiche 1 secret.
Étape 2 : protéger la production
Section intitulée « Étape 2 : protéger la production »C'est ici que l'environnement cesse d'être une étiquette.
-
Ouvrir
production, puis cocher Required reviewers. -
S'ajouter soi-même comme relecteur. Sur un dépôt personnel, vous êtes le seul compte disponible.
-
Laisser activée l'option qui autorise l'auto-approbation. Sans elle, la personne qui déclenche le déploiement ne peut pas l'approuver, et vous bloqueriez votre propre lab. En équipe, c'est l'inverse qu'il faut faire : désactiver l'auto-approbation est précisément ce qui crée le contrôle à quatre yeux.
-
Activer Deployment branches and tags, puis choisir Selected branches and tags et ajouter le motif
main. -
Enregistrer. L'environnement
staging, lui, reste sans règle.
Étape 3 : écrire le workflow de déploiement
Section intitulée « Étape 3 : écrire le workflow de déploiement »Créez .github/workflows/deploy.yml. Le workflow prend un digest en entrée,
le vérifie, le déploie sur staging, puis attend l'approbation avant la
production.
name: Deploy
on: workflow_dispatch: inputs: digest: description: "Digest de l'image à déployer (sha256:...)" required: true type: string
# Aucune permission par défaut.permissions: {}
concurrency: group: deploy-production cancel-in-progress: false
jobs: deploy-staging: name: Déployer sur staging runs-on: ubuntu-24.04 timeout-minutes: 10 environment: name: staging url: https://staging.example.invalid permissions: contents: read packages: read # pull de l'image depuis GHCR attestations: read # lecture de l'attestation de provenance env: IMAGE: ghcr.io/${{ github.repository }} DIGEST: ${{ inputs.digest }} steps: - name: Harden runner uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - name: Login GHCR uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4.5.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Vérifier la provenance env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh attestation verify "oci://${IMAGE}@${DIGEST}" --owner "${GITHUB_REPOSITORY_OWNER}" - name: Déployer (simulation) env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: | if [ -z "${DEPLOY_TOKEN}" ]; then echo "Aucun jeton d'environnement reçu" >&2 exit 1 fi echo "Cible : ${IMAGE}@${DIGEST}" echo "Empreinte : $(printf %s "${DEPLOY_TOKEN}" | sha256sum | cut -c1-12)" docker run --rm --detach --name app-staging "${IMAGE}@${DIGEST}" docker ps --filter name=app-staging
deploy-production: name: Déployer en production needs: deploy-staging runs-on: ubuntu-24.04 timeout-minutes: 10 environment: name: production url: https://example.invalid permissions: contents: read packages: read attestations: read env: IMAGE: ghcr.io/${{ github.repository }} DIGEST: ${{ inputs.digest }} steps: - name: Harden runner uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - name: Login GHCR uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4.5.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Vérifier la provenance env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh attestation verify "oci://${IMAGE}@${DIGEST}" --owner "${GITHUB_REPOSITORY_OWNER}" - name: Déployer (simulation) env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: | if [ -z "${DEPLOY_TOKEN}" ]; then echo "Aucun jeton d'environnement reçu" >&2 exit 1 fi echo "Cible : ${IMAGE}@${DIGEST}" echo "Empreinte : $(printf %s "${DEPLOY_TOKEN}" | sha256sum | cut -c1-12)" docker run --rm --detach --name app-prod "${IMAGE}@${DIGEST}" docker ps --filter name=app-prodQuatre choix méritent un mot. Le digest arrive par env:, jamais interpolé
dans la commande, parce que c'est une entrée fournie par un humain. La
vérification de provenance est répétée dans les deux jobs, parce qu'une
approbation peut durer des heures et que seul le contrôle le plus tardif dit
quelque chose de l'instant du déploiement. cancel-in-progress vaut false
: sur un déploiement, interrompre vaut pire que retarder.
Enfin, le jeton n'est jamais affiché. Le masquage automatique de GitHub ne couvre que la valeur exacte : en publier les premiers caractères le contourne. On imprime donc une empreinte SHA-256 tronquée, qui suffit à prouver que les deux environnements délivrent des secrets différents sans en révéler aucun.
Étape 4 : récupérer le digest à déployer
Section intitulée « Étape 4 : récupérer le digest à déployer »Le digest de la dernière image publiée se lit directement depuis le registre, sans passer par l'API :
docker buildx imagetools inspect ghcr.io/VOTRE-COMPTE/secure-python-pipeline:latest \ --format '{{.Manifest.Digest}}'Conservez la valeur retournée, de la forme sha256:.... Notez-la quelque part :
au moment du rollback, ce sera le digest précédent qu'il faudra retrouver.
Étape 5 : lancer la promotion et observer l'attente
Section intitulée « Étape 5 : lancer la promotion et observer l'attente »gh workflow run deploy.yml -f digest=sha256:VOTRE_DIGESTgh run watchLe comportement attendu, dans l'ordre :
-
deploy-stagingdémarre immédiatement. L'environnementstagingn'a aucune règle. Les logs affichent l'empreinte du jeton de staging. Notez-la. -
deploy-productionpasse en attente. GitHub affiche Review pending deployments, et le job ne démarre pas. -
Les logs de production sont vides. C'est le point à observer : le runner n'a pas démarré, donc il n'a pas reçu
DEPLOY_TOKEN. Le secret de production n'a jamais quitté GitHub. -
Approuver depuis l'interface de l'exécution, bouton Review deployments, cocher
production, puis Approve and deploy. -
Le job démarre et affiche une empreinte différente de celle de staging : c'est la preuve que chaque environnement a délivré son propre secret.
Étape 6 : vérifier la trace laissée
Section intitulée « Étape 6 : vérifier la trace laissée »# Les déploiements enregistrés pour la productiongh api "repos/VOTRE-COMPTE/secure-python-pipeline/deployments?environment=production" \ --jq '.[] | {id, sha, created_at}'Dans l'interface, l'onglet Deployments du dépôt affiche les deux environnements avec leur dernier déploiement et l'URL renseignée dans le workflow. Cette page est ce que vous consulterez en incident pour savoir quelle version tourne où, et depuis quand.
Étape 7 : tester la restriction de branche
Section intitulée « Étape 7 : tester la restriction de branche »La règle posée à l'étape 2 limite la production à main. Vérifions qu'elle mord.
-
Créer une branche et y pousser le workflow tel quel :
Fenêtre de terminal git switch -c test-protectiongit push -u origin test-protection -
Lancer le workflow depuis cette branche :
Fenêtre de terminal gh workflow run deploy.yml --ref test-protection -f digest=sha256:VOTRE_DIGEST -
Observer :
deploy-stagings'exécute,deploy-productionéchoue immédiatement, avec un message indiquant que la branche n'est pas autorisée à déployer sur cet environnement.
Le job échoue avant de démarrer, donc avant toute délivrance de secret. La restriction n'est pas un contrôle applicatif, c'est une porte fermée en amont.
Étape 8 : revenir en arrière
Section intitulée « Étape 8 : revenir en arrière »Le rollback n'a pas de workflow dédié : c'est le même, avec l'autre digest. Encore faut-il qu'il existe un autre digest. Un fork qui ne compte qu'une seule release n'a rien où revenir : publiez-en une seconde avant de continuer.
# Modifier une ligne de l'application, puis publier une nouvelle versiongh release create v1.0.1 --generate-notesLe workflow release.yml construit et atteste alors une seconde image.
C'est vers la première que vous reviendrez.
-
Retrouver le digest précédent, dans vos notes de l'étape 4 ou dans les versions publiées du paquet :
Fenêtre de terminal gh api "user/packages/container/secure-python-pipeline/versions" \--jq '.[] | {digest: .name, tags: .metadata.container.tags, created_at}' -
Relancer le déploiement sur cette valeur :
Fenêtre de terminal gh workflow run deploy.yml -f digest=sha256:DIGEST_PRECEDENT -
Approuver de nouveau la production.
Le retour arrière emprunte exactement le même chemin contrôlé que la mise en production : provenance vérifiée, approbation tracée, déploiement enregistré. Il n'y a aucun rebuild, donc aucun risque nouveau introduit au pire moment.
Validation
Section intitulée « Validation »Le lab est réussi quand ces six affirmations sont vraies sur votre fork :
| Contrôle | Résultat attendu |
|---|---|
| Environnements | staging et production existent, chacun avec son DEPLOY_TOKEN |
| Approbation | La production passe par Review pending deployments |
| Étanchéité des secrets | Aucun log de production avant l'approbation |
| Jetons distincts | Les deux jobs affichent des empreintes de jeton différentes |
| Restriction de branche | Le déploiement production depuis une autre branche échoue |
| Rollback | Le digest précédent se redéploie sans rebuild |
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Concurrency : Le groupement d'exécutions utilisé dans le workflow du lab, détaillé et généralisé.
- Runners : introduction : Choisir et durcir les machines qui exécuteront ces déploiements pour de vrai.