Aller au contenu
English
Cloud medium

Tester votre Terraform en CI sans identifiant cloud

Read this page in English

10 min de lecture

Un pipeline qui teste du Terraform a besoin d'un cloud, et un cloud a besoin d'un compte, d'un secret et d'une facture. feint supprime les trois : l'émulateur entre dans un bloc services: comme n'importe quelle base de données de test, et le vrai provider Terraform applique contre lui. Aucun secret à créer, aucune ressource à nettoyer si le job échoue au milieu.

À la fin de cette page, une pull request déclenchera un apply, un second plan et un destroy complets, sur un runner ordinaire, sans qu'un seul secret cloud n'existe dans le dépôt et sans qu'une ressource facturée soit créée.

  • Choisir entre le service conteneur, l'action dédiée et le binaire.
  • Écrire le job pour GitHub Actions et pour GitLab CI.
  • Comprendre pourquoi le second plan est la seule vraie assertion.
  • Vérifier la signature de l'image avant de l'exécuter.

Le choix se fait sur une seule question : votre job tourne-t-il déjà dans des conteneurs ?

FormeQuand la choisirCe qu'elle apporte
Service conteneurle job utilise déjà des conteneursrien à installer, une image épinglée par digest
Action setup-feintle runner exécute des commandes directementinstalle le binaire, vérifie sa somme de contrôle, attend la réponse
Binaire sur un hôtevous voulez de vraies machines derrière l'APIle seul mode qui donne accès au runtime Incus

Le conteneur est un plan de contrôle et rien d'autre. Il tourne avec --vm off et n'émule que les trois API. Une image qui promettrait de démarrer des conteneurs depuis l'intérieur d'un conteneur serait une demi-vérité, et le projet préfère l'énoncer : les vraies machines demandent le binaire sur un hôte, comme le détaille Faire tourner de vraies machines.

Le job fait la même chose des deux côtés : appliquer, replanifier, détruire. Seule la façon d'attendre l'émulateur change.

Le runner tient les étapes jusqu'à ce que le contrôle de santé de l'image réponde, donc la première étape peut lui parler immédiatement.

name: Terraform contre un cloud local
on:
pull_request:
workflow_dispatch:
permissions: {}
jobs:
terraform:
name: apply, replanifier, detruire
runs-on: ubuntu-24.04
timeout-minutes: 10
services:
feint:
image: ghcr.io/stephrobert/feint:v0.13.0@sha256:d46639ac7c7a0fa2b6b69d0c5d74bdca0757e3124675563209e8ada58e6a6040
ports:
- 4599:4599
env:
SCW_ACCESS_KEY: SCWXXXXXXXXXXXXXXXXX
SCW_SECRET_KEY: 11111111-1111-1111-1111-111111111111
SCW_DEFAULT_PROJECT_ID: 11111111-1111-1111-1111-111111111111
SCW_DEFAULT_ORGANIZATION_ID: 11111111-1111-1111-1111-111111111111
SCW_DEFAULT_ZONE: fr-par-1
SCW_DEFAULT_REGION: fr-par
SCW_API_URL: http://127.0.0.1:4599
SCW_INSECURE: "true"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: 1.15.4
- run: terraform init
- run: terraform apply -auto-approve
- run: terraform plan -detailed-exitcode
- run: terraform destroy -auto-approve

La ligne qui compte est terraform plan -detailed-exitcode, pas l'apply. Un apply réussi prouve que l'API a répondu 200 ou 201 ; il ne prouve pas qu'elle a stocké ce qu'elle a dit avoir stocké.

Le drapeau -detailed-exitcode transforme le plan en assertion :

CodeSignification
0aucun changement, l'infrastructure a convergé
1erreur
2des changements sont proposés, donc dérive

Un émulateur qui répondrait 200 en enregistrant autre chose serait invisible à l'apply et flagrant ici : le second plan proposerait de recréer ce qui existe déjà. C'est l'assertion que la suite de conformité du projet exécute contre chaque fournisseur, et mesurée sur le conteneur v0.13.0, ce second plan sort bien à 0.

Les valeurs du bloc env méritent une explication, sans quoi la première revue de code les prendra pour une fuite.

Elles sont bien formées et sans signification. Le SDK Scaleway valide le format de ces variables avant d'envoyer quoi que ce soit : la clé d'accès ressemble à SCWXXXXXXXXXXXXXXXXX et le secret est un UUID. Ce sont donc des valeurs fausses de la seule manière qui laisse encore démarrer un vrai client.

L'émulateur ne vérifie aucune authentification. Il analyse la forme d'un identifiant et ne valide rien, ce qui est exactement la propriété qui permet de se passer de compte. La contrepartie est nette : un test de permissions IAM n'a aucun sens ici, comme le détaille Ce que feint prouve.

Le site enseigne la sécurité de la chaîne d'approvisionnement, et un émulateur qui tourne dans le pipeline de tout le monde est précisément une surface d'attaque. L'image est signée et attestée par le workflow de release, sous la même identité que les binaires.

Fenêtre de terminal
cosign verify ghcr.io/stephrobert/feint:v0.13.0 \
--certificate-identity-regexp '^https://github\.com/stephrobert/feint/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

La sortie confirme trois attestations distinctes sur le même digest : une nomenclature logicielle au format CycloneDX, une provenance SLSA et la signature elle-même. L'identité vérifiée est la partie décisive de la commande : elle exige que la signature vienne du workflow de release de ce dépôt, sur un tag, et non d'un compte quelconque ayant poussé une image.

Un seul tag par release, et pas de latest. Une référence mutable tire ce qui est le plus récent, c'est-à-dire une image que personne ne peut nommer après coup lorsqu'un test échoue. Épingler le digest dans le pipeline, comme dans les exemples ci-dessus, ferme complètement la question.

SymptômeCauseSolution
Le job échoue immédiatement sur une connexion refuséeGitLab n'attend pas le serviceGarder la boucle d'attente du before_script
terraform plan sort à 2Une divergence réelle entre l'apply et l'état stockéLire le plan : c'est le défaut que ce test cherche
Le SDK refuse de démarrerUne variable d'identifiant mal forméeRespecter les formats : SCWXXXXXXXXXXXXXXXXX et un UUID
Une opération répond 404 sans raisonLe pack décline cette opérationChercher l'en-tête X-Feint-Not-Emulated dans la réponse
Le pipeline est vert mais ne teste rienAucune assertion après l'applyAjouter terraform plan -detailed-exitcode
Des appels partent vers le vrai cloudUne ressource de stockage objet dans la configurationLancer feint doctor dans le répertoire avant l'apply
  • L'émulateur entre dans un bloc services: comme n'importe quelle dépendance de test.
  • Le conteneur est un plan de contrôle : pas de machines, et c'est annoncé plutôt que promis.
  • Le second plan est l'assertion, l'apply n'est qu'un préalable.
  • Les identifiants sont bien formés et vides de sens, ce qui laisse démarrer un vrai client.
  • L'image se vérifie par signature et s'épingle par digest, jamais par latest.
  • GitLab n'attend pas le service : la boucle teste une condition, pas une durée.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn