Aller au contenu
Développement medium

OPA en pratique : opa eval, tests unitaires, Conftest et Gatekeeper

14 min de lecture

Écrire des politiques Rego dans un éditeur n’a de valeur que si elles s’exécutent au bon endroit au bon moment. Ce guide couvre la chaîne complète : évaluation locale avec le CLI opa, tests unitaires automatisés, intégration dans un pipeline CI/CD avec Conftest, et déploiement en admission controller Kubernetes avec Gatekeeper.

  • Installer OPA et évaluer une politique avec opa eval
  • Déboguer une règle undefined avec --explain full
  • Écrire des tests unitaires Rego avec fixtures et with input as
  • Intégrer Conftest dans un pipeline GitHub Actions
  • Déployer un ConstraintTemplate + Constraint dans Kubernetes via Gatekeeper
Fenêtre de terminal
# Linux / macOS (binaire statique, sans dépendance)
curl -Lo opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
chmod +x opa && sudo mv opa /usr/local/bin/
# macOS avec Homebrew
brew install opa
# Vérification
opa version

opa eval est le point d’entrée pour tester n’importe quelle expression Rego sur un input.

Fenêtre de terminal
opa eval \
--input manifest.json \
--data policies/ \
"data.kubernetes.admission.deployment.violations"

Options utiles :

OptionRôle
--input fichier.jsonDocument soumis à évaluation (input)
--data chemin/Fichiers .rego ou .json chargés dans data
--format prettySortie lisible (défaut : JSON brut)
--explain fullTrace complète de l’évaluation (débogage)
--strictActive les vérifications strictes (recommandé)
-b bundle/Charge un bundle OPA

Quand une règle retourne undefined sans raison évidente, --explain full trace chaque étape d’unification :

Fenêtre de terminal
opa eval \
--input manifest.json \
--data policies/ \
--explain full \
--format pretty \
"data.kubernetes.admission.deployment.allow"

La sortie montre quelle expression a échoué en premier dans le corps de chaque règle. C’est le premier réflexe à avoir avant de modifier le code.

Pas besoin d’un fichier pour tester une expression simple :

Fenêtre de terminal
# Tester une fonction built-in
opa eval 'startswith("registry.example.com/api:1.0", "registry.example.com/")'
# Inspecter la structure d'un input
opa eval --input manifest.json 'input.spec.template.spec.containers'

Rego intègre un framework de tests. Un fichier de test est un fichier .rego ordinaire dont les règles commencent par test_.

policies/deployment_test.rego
package kubernetes.admission.deployment_test
import rego.v1
import data.kubernetes.admission.deployment
# --- Fixtures ---
manifest_valide := {
"kind": "Deployment",
"metadata": {
"name": "api",
"namespace": "production",
"labels": {"app": "api", "team": "platform", "env": "prod"}
},
"spec": {"template": {"spec": {
"containers": [{
"name": "api",
"image": "registry.example.com/api:1.4.2",
"securityContext": {"readOnlyRootFilesystem": true}
}]
}}}
}
manifest_image_latest := json.patch(manifest_valide, [{
"op": "replace",
"path": "/spec/template/spec/containers/0/image",
"value": "nginx:latest"
}])
manifest_sans_label := json.patch(manifest_valide, [{
"op": "remove",
"path": "/metadata/labels/app"
}])
# --- Tests ---
test_allow_manifest_valide if {
deployment.allow with input as manifest_valide
}
test_deny_image_latest if {
not deployment.allow with input as manifest_image_latest
count(deployment.violations with input as manifest_image_latest) > 0
}
test_message_image_latest if {
violations := deployment.violations with input as manifest_image_latest
some msg in violations
contains(msg, "nginx:latest")
}
test_deny_label_manquant if {
not deployment.allow with input as manifest_sans_label
}
Fenêtre de terminal
# Tous les tests du répertoire
opa test policies/ lib/ --verbose
# Un fichier spécifique
opa test policies/deployment_test.rego policies/deployment.rego lib/
# Avec couverture de code
opa test policies/ lib/ --coverage

Sortie avec --verbose :

PASS: test_allow_manifest_valide (1.2ms)
PASS: test_deny_image_latest (0.8ms)
PASS: test_message_image_latest (0.9ms)
PASS: test_deny_label_manquant (0.7ms)
--------------------------------------------------------------------------------
PASS: 4/4

Conftest est un outil CLI qui applique des politiques Rego à n’importe quel fichier de configuration : manifests Kubernetes, plans Terraform, Dockerfiles, fichiers GitHub Actions, etc.

Fenêtre de terminal
# Linux
curl -Lo conftest https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_Linux_x86_64.tar.gz
tar xzf conftest_Linux_x86_64.tar.gz && sudo mv conftest /usr/local/bin/
# macOS
brew install conftest

Conftest attend les politiques dans un dossier policy/ par convention. Le namespace utilisé est main par défaut.

projet/
├── policy/
│ ├── kubernetes.rego
│ ├── terraform.rego
│ └── lib/
│ └── helpers.rego
├── manifests/
│ └── deployment.yaml
└── main.tf

Conftest reconnaît trois règles spéciales : deny, warn et violation. deny bloque avec code de sortie non nul. warn avertit sans bloquer.

policy/kubernetes.rego
package main
import rego.v1
# Bloquant
deny contains msg if {
input.kind == "Deployment"
some c in input.spec.template.spec.containers
endswith(c.image, ":latest")
msg := sprintf("[ERREUR] conteneur '%v' : tag latest interdit", [c.name])
}
# Non bloquant
warn contains msg if {
input.kind == "Deployment"
not input.metadata.labels.team
msg := "[AVERTISSEMENT] label 'team' recommandé mais absent"
}
Fenêtre de terminal
# Un manifest
conftest test manifests/deployment.yaml
# Plusieurs manifests
conftest test manifests/
# Un plan Terraform (après terraform show -json)
terraform show -json plan.tfplan > plan.json
conftest test --parser json plan.json --policy policy/terraform.rego
# Un Dockerfile
conftest test Dockerfile --policy policy/docker.rego

Sortie :

FAIL - manifests/deployment.yaml - main - [ERREUR] conteneur 'api' : tag latest interdit
WARN - manifests/deployment.yaml - main - [AVERTISSEMENT] label 'team' recommandé mais absent
2 tests, 0 passed, 1 warning, 1 failure
.github/workflows/policy.yml
name: Policy Check
on: [push, pull_request]
permissions: {}
jobs:
conftest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: Install Conftest
run: |
curl -Lo conftest.tar.gz \
https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_Linux_x86_64.tar.gz
tar xzf conftest.tar.gz
sudo mv conftest /usr/local/bin/
- name: Validate Kubernetes manifests
run: conftest test manifests/ --policy policy/
- name: Validate Terraform plan
run: |
# Suppose que le plan JSON est déjà généré
conftest test --parser json plan.json --policy policy/terraform.rego

Le job échoue automatiquement si une règle deny est déclenchée. Les warn apparaissent dans les logs sans bloquer le pipeline.

OPA Gatekeeper déploie OPA comme webhook d’admission dans Kubernetes. Chaque ressource créée ou modifiée dans le cluster passe par vos politiques avant d’être acceptée.

Fenêtre de terminal
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.17/deploy/gatekeeper.yaml

Vérifiez que les pods sont prêts :

Fenêtre de terminal
kubectl get pods -n gatekeeper-system

Gatekeeper introduit deux CRDs :

  • ConstraintTemplate : contient la logique Rego et définit un nouveau type de ressource Kubernetes.
  • Constraint : instancie ce type en ciblant des ressources spécifiques avec des paramètres.
constrainttemplate-image-registry.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredregistry
spec:
crd:
spec:
names:
kind: K8sRequiredRegistry
validation:
openAPIV3Schema:
type: object
properties:
registres_autorises:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredregistry
import rego.v1
violation contains {"msg": msg} if {
some c in input.review.object.spec.template.spec.containers
registre := split(c.image, "/")[0]
not registre in input.parameters.registres_autorises
msg := sprintf(
"conteneur '%v' : registre '%v' non autorisé",
[c.name, registre]
)
}
Fenêtre de terminal
kubectl apply -f constrainttemplate-image-registry.yaml
# Vérifiez que le CRD est bien créé
kubectl get constrainttemplate k8srequiredregistry
constraint-image-registry-prod.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredRegistry
metadata:
name: registry-autorise-production
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
namespaces: ["production", "staging"]
parameters:
registres_autorises:
- "registry.example.com"
- "gcr.io/mon-projet"
Fenêtre de terminal
kubectl apply -f constraint-image-registry-prod.yaml

Testez la contrainte en appliquant un Deployment avec une image non conforme :

Fenêtre de terminal
kubectl run test-nginx \
--image=nginx:latest \
--namespace=production \
--dry-run=server
# Résultat attendu :
# Error: admission webhook "validation.gatekeeper.sh" denied the request:
# [registry-autorise-production] conteneur 'test-nginx' : registre 'docker.io' non autorisé

Gatekeeper peut aussi auditer les ressources déjà déployées et reporter les violations sans les bloquer. C’est indispensable lors d’un déploiement progressif.

spec:
enforcementAction: dryrun # warn | deny | dryrun
Fenêtre de terminal
# Voir les violations détectées en mode audit
kubectl get k8srequiredregistry registry-autorise-production -o json \
| jq '.status.violations'
BesoinOutil
Tester une expression Rego localementopa eval
Tests unitaires automatisésopa test
Valider des manifests / IaC en CI/CDConftest
Contrôle d’admission KubernetesGatekeeper
Autorisation microservices / APIOPA server (API REST)
Distribution centralisée de politiquesOPA Bundles
  • opa eval --explain full est le premier outil de débogage quand une règle retourne undefined.
  • Les tests unitaires Rego utilisent with input as fixture — pas de mock, pas de stub, juste de la substitution d’input.
  • Conftest utilise deny / warn dans le namespace main et s’intègre directement dans un pipeline sans infrastructure supplémentaire.
  • Gatekeeper sépare la logique (ConstraintTemplate) du ciblage (Constraint) : vous pouvez réutiliser le même template pour différents namespaces ou clusters.
  • Commencez toujours par enforcementAction: dryrun en production — auditez avant de bloquer.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn