TP 34 : Policy as Code avec OPA et Conftest
TP 34 : Policy as Code avec OPA et Conftest
Section intitulée « TP 34 : Policy as Code avec OPA et Conftest »Objectifs
Section intitulée « Objectifs »À l’issue de ce TP, vous serez capable de :
- exporter un plan Terraform au format JSON exploitable ;
- écrire des politiques Rego (OPA) ciblant les ressources planifiées ;
- exécuter les politiques avec Conftest et bloquer un plan non conforme ;
- distinguer une règle bloquante (
deny) d’un avertissement (warn) ; - intégrer le contrôle dans la pipeline.
Prérequis
Section intitulée « Prérequis »- Terraform
>= 1.5. - Conftest
>= 0.56(embarque OPA). Vérifiez :conftest --version. - Notions de la pipeline (TP 29).
Contexte
Section intitulée « Contexte »InfraBank veut interdire, de façon automatique et centralisée, certaines configurations : buckets sans chiffrement, ressources sans tags obligatoires, ports SSH ouverts au monde. Ces règles s’expriment en Policy as Code et s’appliquent sur le plan avant tout apply.
Étape 1 : Une configuration volontairement non conforme
Section intitulée « Étape 1 : Une configuration volontairement non conforme »terraform { required_version = ">= 1.5" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.60" } }}
provider "aws" { region = "eu-west-3" # Options pour planifier sans credentials réels : skip_credentials_validation = true skip_requesting_account_id = true}resource "aws_s3_bucket" "sans_tags" { bucket = "infrabank-non-conforme" # ni tags obligatoires, ni chiffrement déclaré}
resource "aws_security_group" "ouvert" { name = "trop-ouvert"
ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # SSH ouvert au monde }}Étape 2 : Exporter le plan en JSON
Section intitulée « Étape 2 : Exporter le plan en JSON »Conftest travaille sur le plan au format JSON, pas sur le .tf.
terraform initterraform plan -out=tfplan.binaryterraform show -json tfplan.binary > plan.jsonInspectez la structure : les ressources planifiées sont dans .resource_changes[], avec type, change.actions et change.after.
jq '.resource_changes[] | {type, actions: .change.actions}' plan.jsonÉtape 3 : Écrire les politiques Rego
Section intitulée « Étape 3 : Écrire les politiques Rego »Créez un dossier policy/. Conftest cherche par défaut le package main.
package main
import rego.v1
required_tags := {"owner", "environment"}
deny contains msg if { some rc in input.resource_changes rc.type == "aws_s3_bucket" "create" in rc.change.actions
provided := object.keys(object.get(rc.change.after, "tags", {})) missing := required_tags - {t | some t in provided} count(missing) > 0
msg := sprintf( "Bucket %s: tags obligatoires manquants: %v", [rc.change.after.bucket, missing], )}package main
import rego.v1
deny contains msg if { some rc in input.resource_changes rc.type == "aws_security_group" some rule in rc.change.after.ingress rule.from_port == 22 "0.0.0.0/0" in rule.cidr_blocks
msg := sprintf( "Security group %s: SSH (22) ouvert à 0.0.0.0/0 interdit", [rc.change.after.name], )}package main
import rego.v1
# Avertissement (non bloquant) : chiffrement du bucket recommandé.warn contains msg if { some rc in input.resource_changes rc.type == "aws_s3_bucket" "create" in rc.change.actions
msg := sprintf( "Bucket %s: pensez à déclarer une configuration de chiffrement dédiée", [rc.change.after.bucket], )}Conventions Conftest : les règles nommées deny font échouer le test ; warn produisent un avertissement sans échec.
Étape 4 : Exécuter Conftest
Section intitulée « Étape 4 : Exécuter Conftest »conftest test plan.json --policy policySortie attendue (extrait) :
FAIL - plan.json - main - Bucket infrabank-non-conforme: tags obligatoires manquants: {"owner", "environment"}FAIL - plan.json - main - Security group trop-ouvert: SSH (22) ouvert à 0.0.0.0/0 interditWARN - plan.json - main - Bucket infrabank-non-conforme: pensez à déclarer une configuration de chiffrement dédiée
2 tests, 0 passed, 1 warning, 2 failuresLe code de sortie non nul fait échouer la pipeline.
Étape 5 : Corriger et re-tester
Section intitulée « Étape 5 : Corriger et re-tester »resource "aws_s3_bucket" "conforme" { bucket = "infrabank-conforme"
tags = { owner = "plateforme" environment = "prod" }}
resource "aws_security_group" "restreint" { name = "sg-restreint"
ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["10.0.0.0/16"] # plage interne }}terraform plan -out=tfplan.binaryterraform show -json tfplan.binary > plan.jsonconftest test plan.json --policy policy# Plus aucune règle deny déclenchéeÉtape 6 : Intégrer à la pipeline
Section intitulée « Étape 6 : Intégrer à la pipeline »Ajoutez une étape après le plan du TP 29 :
- name: Policy as Code (Conftest) run: | terraform show -json tfplan > plan.json conftest test plan.json --policy policyÉpinglez la version de Conftest sur le runner (via une action dédiée ou un binaire de version fixe) pour rester reproductible.
Vérification
Section intitulée « Vérification »- Le plan non conforme déclenche au moins deux
denyet unwarn. - Le plan corrigé passe sans
deny. - Le code de sortie de Conftest est non nul en cas de
deny.
Nettoyage
Section intitulée « Nettoyage »rm -f tfplan.binary plan.jsonrm -rf .terraform