Conftest dans le CI : policies YAML et Terraform
À la fin de ce tutoriel, vous saurez installer Conftest, écrire des policies Rego sur des manifests Kubernetes YAML et des plans Terraform JSON, brancher un job fail-closed dans GitHub Actions (variante GitLab CI), et bloquer un merge qui viole vos garde-fous — sans attendre Gatekeeper en cluster.
Niveau : Intermédiaire · Temps estimé : 45–60 min · Versions testées : Conftest (OPA), Rego, Terraform 1.5+, kubectl manifests, GitHub Actions, GitLab CI · Dernière vérification : 2026-09-11 · Region :
ca-central-1Slug :
wow-conftest-ci· Série : WOW (10/50) · Mot-clé SEO : conftest ci · Publish : READY (feu vert Maître)← Précédent : Policy-as-Code OPA/Gatekeeper · → Suivant : HashiCorp Vault pour secrets DevOps · Aussi : Tests Terraform 2026 · Trivy en CI
Prérequis
- Bases Git + CI (GitHub Actions ou GitLab CI)
- Notions Kubernetes (Deployment, SecurityContext) — Kubernetes
- Terraform installé localement pour le lab plan JSON — Installer Terraform
- Optionnel : profil AWS
labenca-central-1si vous générez un vrai plan S3 - Aucun cluster Gatekeeper requis pour ce lab : Conftest tourne hors cluster, dans le pipeline
Coût estimé : 0 € en local / runners CI. Pas de ressources AWS obligatoires ; si vous créez un bucket de lab, détruisez-le (terraform destroy) et restez Free Tier.
export AWS_PROFILE=lab
export AWS_DEFAULT_REGION=ca-central-1
Ce que nous allons construire
Lab Conftest CI (WOW 10/50) — ca-central-1
├── policy/k8s : deny privileged + require resources
├── policy/tf : deny S3 public + require tags + region
├── fixtures YAML + terraform show -json
├── GitHub Actions : conftest test (fail-closed)
├── Variante GitLab CI
└── Troubleshooting + FAQ + quiz
(Schéma — alt : « Pull request → Conftest (Rego) sur YAML K8s et plan Terraform → merge seulement si policies OK ».)
Étape 1 — Pourquoi Conftest dans le CI ?
OPA (Open Policy Agent) évalue des policies Rego. Gatekeeper applique OPA dans Kubernetes (admission). Conftest applique le même moteur sur des fichiers : YAML, JSON, HCL via JSON, Dockerfile, etc. — idéal avant le merge.
| Outil | Quand | Force | Limite |
|---|---|---|---|
| Conftest | CI / pre-commit / local | Rapide, multi-formats, fail-closed | Ne voit pas le runtime cluster |
| Gatekeeper | Admission K8s | Bloque kubectl apply live |
Trop tard si le YAML est déjà mergé |
| terraform test / OPA TF | Plan / modules | Couverture IaC profonde | Moins « multi-YAML » générique |
| Checkov / tfsec | Scan IaC | Règles prêtes | Moins flexible qu’un Rego maison |
Chez DevOps Elastic Hayway, le pattern gagnant : Conftest en CI (shift-left) + OPA/Gatekeeper en prod (défense en profondeur). Voir aussi Policy-as-Code OPA/Gatekeeper et Tests Terraform 2026.
Étape 2 — Installer Conftest
# Linux amd64 — adaptez la version sur https://github.com/open-policy-agent/conftest/releases
CONFTEST_VERSION=0.56.0
curl -sL "https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz"
| tar -xz conftest
sudo mv conftest /usr/local/bin/
conftest --version
Sous macOS : brew install conftest. Sur les runners GitHub, préférez l’image officielle openpolicyagent/conftest plutôt qu’un binaire ad hoc.
Initialisez le lab :
mkdir -p ~/lab-conftest-ci/{policy/k8s,policy/tf,fixtures/k8s,tf-lab}
cd ~/lab-conftest-ci
Étape 3 — Première policy Rego sur YAML Kubernetes
Créez un Deployment volontairement mauvais (privileged, sans requests/limits) :
cat > fixtures/k8s/bad-deploy.yaml << 'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-bad
spec:
replicas: 1
selector:
matchLabels: { app: api }
template:
metadata:
labels: { app: api }
spec:
containers:
- name: api
image: ghcr.io/example/api:1.0.0
securityContext:
privileged: true
YAML
Policy Conftest (package = dossier relatif sous policy/) :
cat > policy/k8s/deny.rego << 'REGO'
package main
deny[msg] {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
c.securityContext.privileged == true
msg := sprintf("container %q ne doit pas être privileged", [c.name])
}
deny[msg] {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
not c.resources.requests.cpu
msg := sprintf("container %q : resources.requests.cpu obligatoire", [c.name])
}
deny[msg] {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
not c.resources.limits.memory
msg := sprintf("container %q : resources.limits.memory obligatoire", [c.name])
}
REGO
Testez :
conftest test fixtures/k8s/bad-deploy.yaml --policy policy/k8s
echo exit:$?
Conftest doit échouer (exit ≠ 0) avec les messages deny. Corrigez le fixture (privileged false + requests/limits) et relancez jusqu’à PASS. C’est le cœur du HowTo : la CI doit échouer comme votre terminal.
Étape 4 — Policies Terraform via JSON de plan
Conftest lit du JSON. Générez un plan Terraform, puis terraform show -json :
cd ~/lab-conftest-ci/tf-lab
cat > main.tf << 'TF'
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "ca-central-1" }
resource "aws_s3_bucket" "lab" {
bucket = "deh-conftest-lab-CHANGE-ME"
tags = { Project = "deh-lab", Owner = "platform" }
}
# Exemple volontairement risqué à bloquer en policy :
# resource "aws_s3_bucket_public_access_block" "lab" { ... public ACL ... }
TF
Sans credentials AWS, vous pouvez committer un plan JSON de fixture (généré une fois en lab). Structure utile pour Rego : resource_changes[_].type, .change.after, .change.actions.
cat > ../policy/tf/s3.rego << 'REGO'
package main
deny[msg] {
rc := input.resource_changes[_]
rc.type == "aws_s3_bucket"
after := rc.change.after
after.acl == "public-read"
msg := sprintf("S3 %v : acl public-read interdit", [rc.address])
}
deny[msg] {
rc := input.resource_changes[_]
rc.type == "aws_s3_bucket"
after := rc.change.after
not after.tags.Project
msg := sprintf("S3 %v : tag Project obligatoire", [rc.address])
}
deny[msg] {
# provider_config / planned values selon version terraform show -json
cfg := input.configuration.provider_config.aws
region := cfg.expressions.region.constant_value
region != "ca-central-1"
msg := sprintf("région provider AWS %v : seules ca-central-1 autorisée en lab DEH", [region])
}
REGO
Exécution typique :
terraform init -backend=false
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy ../policy/tf
Astuce DEH : gardez plan.json en artefact CI éphémère (noms/ARNs possibles, pas de secrets).
Étape 5 — Brancher Conftest dans GitHub Actions
Workflow fail-closed sur PR (YAML + plan TF si présent) :
# .github/workflows/conftest.yml
name: conftest-policies
on:
pull_request:
paths:
- "deploy/**/*.yaml"
- "infra/**"
- "policy/**"
- ".github/workflows/conftest.yml"
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Conftest K8s YAML
uses: docker://openpolicyagent/conftest:latest
with:
args: test deploy/ --policy policy/k8s
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- name: Terraform plan JSON
working-directory: infra
run: |
terraform init -backend=false
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
- name: Conftest Terraform plan
uses: docker://openpolicyagent/conftest:latest
with:
args: test infra/plan.json --policy policy/tf
Lecture repo suffit sans secrets si init -backend=false. En prod DEH, OIDC (GitHub Actions OIDC AWS) pour un plan réel ca-central-1. Check required en branch protection.
Étape 6 — Variante GitLab CI
# .gitlab-ci.yml (extrait)
stages: [policy]
conftest-k8s:
stage: policy
image:
name: openpolicyagent/conftest:latest
entrypoint: [""]
script:
- conftest test deploy/ --policy policy/k8s
conftest-tf:
stage: policy
image: hashicorp/terraform:1.9
script:
- cd infra && terraform init -backend=false
- terraform plan -out=tfplan && terraform show -json tfplan > plan.json
- apk add --no-cache curl && curl -sL ... # ou image multi-stage avec conftest
- conftest test plan.json --policy ../policy/tf
Préférez une image ci-conftest (Terraform + Conftest) pour éviter d’installer à chaque job.
Étape 7 — Bundles et fail-closed
- Séparez packages Rego (
k8s.security,tf.aws) si vous mutualisez plusieurs dossiers. - Versionnez
policy/(repo app ouplatform-policies) ; taggez les releases comme du code. - Fail-closed : exit ≠ 0 = job rouge. Jamais
continue-on-error: true« pour voir ». - Exceptions : data files Rego documentés (owner, ticket, expiry) plutôt que commenter la règle.
- Complément : Conftest ≠ CVE — ajoutez Trivy.
Troubleshooting
| Symptôme | Cause / correction |
|---|---|
no policies found |
Mauvais --policy path ; package Rego attendu sous ce dossier |
| Always PASS alors que deny attendu | Mauvais input : testez avec conftest parse / inspectez le JSON |
| Rego compile error | conftest verify --policy policy/ ; regardez [_] vs règles if selon version |
| Plan JSON énorme / lent | Filtrez resource_changes ou découpez policies par type |
| OK en local, KO en CI | Versions Conftest/Terraform différentes ; pinez le tag image |
| Gatekeeper OK, CI KO (ou inverse) | Policies non synchronisées — même source Rego pour Conftest et contraintes |
FAQ
Conftest remplace-t-il Gatekeeper ?
Non. Conftest shift-left en CI ; Gatekeeper (ou Kyverno) reste la barrière admission cluster. Les deux se complètent.
HCL brut ou JSON de plan ?
Le plan JSON (terraform show -json) est le contrat le plus fiable pour l’IaC appliqué. Le HCL source peut diverger du plan réel (modules, locals).
Où mettre les policies ?
Dans le repo applicatif pour commencer (policy/), puis extrayez un repo platform-policies quand plusieurs équipes partagent les mêmes règles.
Peut-on tester sans AWS ?
Oui : fixtures JSON de plan + terraform init -backend=false. Gardez ca-central-1 dans les exemples DEH même en mock.
Rego est-il obligatoire ?
Conftest est centré Rego/OPA. Pour des règles « batteries included » sans Rego, regardez Checkov/tfsec — moins flexibles pour du métier custom.
Quiz (5 questions)
1. Conftest évalue surtout :
– A. Uniquement l’admission K8s en live
– B. Des fichiers (YAML/JSON/…) via policies Rego, souvent en CI
– C. Les CVE d’images Docker
2. Pour Terraform, le flux robuste est :
– A. Lire le README du module
– B. terraform plan puis terraform show -json + conftest test
– C. Committer l’état terraform.tfstate dans Git
3. Un job Conftest avec continue-on-error: true :
– A. Renforce le fail-closed
– B. Affaiblit la barrière (deny ignoré)
– C. Remplace Gatekeeper
4. La région lab DEH dans ce tuto est :
– A. us-east-1 uniquement
– B. ca-central-1
– C. eu-west-3 obligatoire
5. Conftest + Trivy ensemble :
– A. Redondants à 100 %
– B. Complémentaires : policies métier (Conftest) + CVE/misconfig (Trivy)
– C. Interdits sur GitHub Actions
Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑B
Pour aller plus loin
- Conftest documentation
- OPA / Rego
- Policy-as-Code OPA/Gatekeeper (WOW 9)
- Tests Terraform 2026 : terraform test + OPA (WOW 8)
- Trivy : scan images et IaC en CI (WOW 31)
- GitHub Actions OIDC AWS
- Pipeline DevSecOps de A à Z
Maillage série WOW
| ← Précédent | Policy-as-Code OPA/Gatekeeper |
| → Suivant | HashiCorp Vault pour secrets DevOps |
| Aussi | Tests Terraform · Trivy · DevSecOps |
Meta publication (SEO)
- Title SEO : Conftest dans le CI : policies YAML et Terraform (guide FR)
- Meta description : Conftest + Rego en CI : policies Kubernetes YAML et plan Terraform JSON, lab GitHub Actions / GitLab fail-closed, FAQ. Région ca-central-1 — DevOps Elastic Hayway.
- Focus keyword : conftest ci
- Secondary : conftest terraform, conftest kubernetes, policy as code ci, rego ci
- Image :
assets/web/devopelastichayway/cover-wow-conftest-ci-1200x630.webp(à générer) - Catégorie : WOW / Policy-as-Code · Niveau : Intermédiaire
- URL cible : https://devopelastichayway.com/tutoriels/wow-conftest-ci/
- Post live : N/A (nouveau) · slug
wow-conftest-ci· Publish : READY (auto-push WP-CLI autorisé)
Pour aller plus loin — hubs live
Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.