Aller au contenu

TP 34 : Policy as Code avec OPA et Conftest

À 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.
  • Terraform >= 1.5.
  • Conftest >= 0.56 (embarque OPA). Vérifiez : conftest --version.
  • Notions de la pipeline (TP 29).

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 »
versions.tf
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
}
main.tf
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
}
}

Conftest travaille sur le plan au format JSON, pas sur le .tf.

Fenêtre de terminal
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > plan.json

Inspectez la structure : les ressources planifiées sont dans .resource_changes[], avec type, change.actions et change.after.

Fenêtre de terminal
jq '.resource_changes[] | {type, actions: .change.actions}' plan.json

Créez un dossier policy/. Conftest cherche par défaut le package main.

policy/tags.rego
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],
)
}
policy/network.rego
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],
)
}
policy/encryption.rego
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.

Fenêtre de terminal
conftest test plan.json --policy policy

Sortie 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 interdit
WARN - plan.json - main - Bucket infrabank-non-conforme: pensez à déclarer une configuration de chiffrement dédiée
2 tests, 0 passed, 1 warning, 2 failures

Le code de sortie non nul fait échouer la pipeline.

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
}
}
Fenêtre de terminal
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > plan.json
conftest test plan.json --policy policy
# Plus aucune règle deny déclenchée

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.

  • Le plan non conforme déclenche au moins deux deny et un warn.
  • Le plan corrigé passe sans deny.
  • Le code de sortie de Conftest est non nul en cas de deny.
Fenêtre de terminal
rm -f tfplan.binary plan.json
rm -rf .terraform