É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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Installer OPA et évaluer une politique avec
opa eval - Déboguer une règle
undefinedavec--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
Prérequis
Section intitulée « Prérequis »- Syntaxe de base de Rego
- Données et itération
- Règles et fonctions
- Un cluster Kubernetes pour la section Gatekeeper (minikube ou kind suffit)
Installer OPA
Section intitulée « Installer OPA »OPA se distribue en binaire statique unique, sans runtime ni dépendance
système : c'est ce qui permet de le poser aussi bien sur un poste que dans une
image de conteneur scratch. Le projet publie, à côté de chaque binaire, un
fichier .sha256 contenant son empreinte. Téléchargez toujours les deux et
faites vérifier l'empreinte par sha256sum --check avant de rendre le fichier
exécutable : un binaire de politique de sécurité altéré en transit validerait
silencieusement tout ce que vous croyez bloquer.
# Linux (binaire statique, sans dépendance)curl -fsSLO https://openpolicyagent.org/downloads/latest/opa_linux_amd64_staticcurl -fsSLO https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static.sha256
sha256sum --check opa_linux_amd64_static.sha256
chmod +x opa_linux_amd64_staticsudo install -m 0755 opa_linux_amd64_static /usr/local/bin/opa
# macOS avec Homebrew (la formule vérifie déjà l'empreinte)brew install opa
# Vérificationopa versionLa sortie de sha256sum --check doit afficher opa_linux_amd64_static: OK.
Toute autre réponse impose de recommencer le téléchargement ; ne passez jamais
outre avec --ignore-missing ou en supprimant la vérification.
opa eval : évaluer une politique
Section intitulée « opa eval : évaluer une politique »opa eval est le point d'entrée pour tester n'importe quelle expression Rego sur un input.
opa eval \ --input manifest.json \ --data policies/ \ "data.kubernetes.admission.deployment.violations"Options utiles :
| Option | Rôle |
|---|---|
--input fichier.json | Document soumis à évaluation (input) |
--data chemin/ | Fichiers .rego ou .json chargés dans data |
--format pretty | Sortie lisible (défaut : JSON brut) |
--explain full | Trace complète de l'évaluation (débogage) |
--strict | Active les vérifications strictes (recommandé) |
-b bundle/ | Charge un bundle OPA |
Déboguer avec --explain
Section intitulée « Déboguer avec --explain »Quand une règle retourne undefined sans raison évidente, --explain full trace chaque étape d'unification :
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.
Évaluer une expression inline
Section intitulée « Évaluer une expression inline »Sans --data, opa eval évalue directement l'expression passée en argument,
ce qui en fait une calculatrice à fonctions Rego. C'est le moyen le plus rapide
de lever un doute sur le comportement exact d'une fonction intégrée avant
de l'utiliser dans une politique. Combiné à --input, le même appel sert à
explorer la structure d'un document réel : plutôt que de deviner un chemin
d'accès, vous le vérifiez.
# Tester une fonction built-inopa eval 'startswith("registry.example.com/api:1.0", "registry.example.com/")'
# Inspecter la structure d'un inputopa eval --input manifest.json 'input.spec.template.spec.containers'opa test : tests unitaires
Section intitulée « opa test : tests unitaires »Rego intègre un framework de tests. Un fichier de test est un fichier .rego ordinaire dont les règles commencent par test_.
Structure d'un fichier de test
Section intitulée « Structure d'un fichier de test »Trois conventions structurent ce fichier. Le package de test est distinct de
celui de la politique (suffixe _test), ce qui évite que les fixtures polluent
l'espace de noms évalué en production. Les fixtures sont de simples valeurs
Rego dérivées les unes des autres par json.patch, sans framework ni mock : un
test Rego ne simule rien, il substitue un input complet. Les règles de test
commencent obligatoirement par test_, c'est ce préfixe que opa test cherche.
Notez surtout la forme du deuxième test : le modificateur with s'applique à
une expression entière et ne peut pas être glissé dans un argument de
fonction, il faut donc affecter le résultat à une variable avant de le compter.
package kubernetes.admission.deployment_test
import rego.v1import 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 violations := deployment.violations with input as manifest_image_latest count(violations) > 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}Lancer les tests
Section intitulée « Lancer les tests »opa test charge tous les fichiers .rego des chemins fournis, politiques
et tests confondus : oublier le répertoire des bibliothèques partagées produit
une erreur de compilation, pas un test rouge. Le --verbose affiche une ligne
par test avec sa durée, ce qui sert surtout à repérer une règle dont
l'évaluation dérape. Le --coverage sort un rapport JSON indiquant les lignes
de politique jamais atteintes ; c'est le seul moyen fiable de détecter une règle
morte que personne n'exerce.
# Tous les tests du répertoireopa test policies/ lib/ --verbose
# Un fichier spécifiqueopa test policies/deployment_test.rego policies/deployment.rego lib/
# Avec couverture de codeopa test policies/ lib/ --coverageSortie avec --verbose :
policies/deployment_test.rego:38:data.kubernetes.admission.deployment_test.test_allow_manifest_valide: PASS (569.683µs)policies/deployment_test.rego:54:data.kubernetes.admission.deployment_test.test_deny_label_manquant: PASS (574.448µs)policies/deployment_test.rego:48:data.kubernetes.admission.deployment_test.test_message_image_latest: PASS (634.582µs)policies/deployment_test.rego:42:data.kubernetes.admission.deployment_test.test_deny_image_latest: PASS (979.831µs)--------------------------------------------------------------------------------PASS: 4/4L'ordre d'affichage n'est pas celui du fichier : opa test évalue les règles en
parallèle. Fiez-vous au numéro de ligne rappelé au-dessus de chaque résultat.
Conftest : validation en CI/CD
Section intitulée « Conftest : validation en CI/CD »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.
Installation
Section intitulée « Installation »Les archives publiées par Conftest portent le numéro de version dans leur
nom (conftest_0.68.2_Linux_x86_64.tar.gz), il faut donc cibler un tag précis
plutôt que latest/download. Chaque release publie également un fichier
checksums.txt couvrant toutes les archives : on en extrait la ligne qui nous
concerne et on la passe à sha256sum --check avant d'extraire quoi que ce soit.
# LinuxCONFTEST_VERSION=0.68.2BASE="https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}"
curl -fsSLO "${BASE}/conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz"curl -fsSLO "${BASE}/checksums.txt"
grep "conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" checksums.txt \ | sha256sum --check -
tar xzf "conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" conftestsudo install -m 0755 conftest /usr/local/bin/conftest
# macOSbrew install conftestVérifiez avec conftest --version : la sortie affiche la version de Conftest
et celle d'OPA embarquée. Cette seconde ligne compte, c'est elle qui
détermine les fonctions Rego disponibles dans vos politiques.
Structure de projet Conftest
Section intitulée « Structure de projet 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.tfPolitiques Conftest
Section intitulée « Politiques Conftest »Conftest reconnaît trois règles spéciales : deny, warn et violation. deny bloque avec code de sortie non nul. warn avertit sans bloquer.
package main
import rego.v1
# Bloquantdeny 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 bloquantwarn contains msg if { input.kind == "Deployment" not input.metadata.labels.team msg := "[AVERTISSEMENT] label 'team' recommandé mais absent"}Valider des fichiers
Section intitulée « Valider des fichiers »Conftest devine le format d'entrée à partir de l'extension : YAML, JSON, HCL,
Dockerfile, INI et quelques autres. Le --parser ne sert que lorsque
l'extension ment, typiquement un plan Terraform exporté en JSON. Retenez
également que Conftest découpe les fichiers YAML multi-documents et évalue
chaque document séparément : un fichier contenant un Deployment et un Service
produit deux évaluations, d'où le compte de tests parfois supérieur au nombre
de fichiers.
# Un manifestconftest test manifests/deployment.yaml
# Plusieurs manifestsconftest test manifests/
# Un plan Terraform (après terraform show -json)terraform show -json plan.tfplan > plan.jsonconftest test --parser json plan.json --policy policy/terraform.rego
# Un Dockerfileconftest test Dockerfile --policy policy/docker.regoSortie :
WARN - manifests/deployment.yaml - main - [AVERTISSEMENT] label 'team' recommandé mais absentFAIL - manifests/deployment.yaml - main - [ERREUR] conteneur 'api' : tag latest interdit
2 tests, 0 passed, 1 warning, 1 failure, 0 exceptionsLe code de sortie vaut 1 dès qu'une règle deny se déclenche, 0 si
seules des règles warn remontent. C'est ce code, pas le texte, que votre
pipeline doit interpréter.
Intégration GitHub Actions
Section intitulée « Intégration GitHub Actions »Trois choix de durcissement dans ce workflow méritent d'être copiés tels quels.
Les permissions sont vides au niveau du workflow et redonnées au strict
minimum sur le job, ce qui empêche le GITHUB_TOKEN d'écrire quoi que ce soit
si une étape est compromise. L'action checkout est épinglée par SHA et non
par tag, un tag pouvant être redéplacé vers un autre commit par son mainteneur.
Le persist-credentials: false empêche le jeton de rester dans
.git/config où n'importe quelle étape suivante pourrait le lire.
name: Policy Check
on: [push, pull_request]
permissions: {}
env: CONFTEST_VERSION: "0.68.2"
jobs: conftest: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Install Conftest run: | BASE="https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}" curl -fsSLO "${BASE}/conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" curl -fsSLO "${BASE}/checksums.txt" grep "conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" checksums.txt \ | sha256sum --check - tar xzf "conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" conftest sudo install -m 0755 conftest /usr/local/bin/conftest
- 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.regoLe job échoue automatiquement si une règle deny est déclenchée. Les warn apparaissent dans les logs sans bloquer le pipeline.
Gatekeeper : admission controller Kubernetes
Section intitulée « Gatekeeper : admission controller Kubernetes »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.
Installation
Section intitulée « Installation »Le manifeste ci-dessous installe le namespace gatekeeper-system, les CRD, le
contrôleur et surtout la configuration de webhook d'admission. C'est cette
dernière qui rend l'opération non anodine : à partir de son application, toute
création de ressource dans le cluster passe par un appel réseau vers le service
Gatekeeper. Si le contrôleur ne démarre pas, l'API server voit un webhook
injoignable et le comportement dépend du failurePolicy du manifeste. Épinglez
la branche de release et vérifiez les pods avant de déclarer la moindre
contrainte.
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.23/deploy/gatekeeper.yamlVérifiez que les pods sont prêts :
kubectl get pods -n gatekeeper-systemLe modèle en deux ressources
Section intitulée « Le modèle en deux ressources »Gatekeeper sépare volontairement la règle de son application, et c'est ce
découpage qu'il faut comprendre avant d'écrire la moindre ligne de YAML. Un
ConstraintTemplate seul ne bloque rien : il ne fait qu'ajouter un type de
ressource au cluster. C'est la Constraint qui active la règle, sur un
périmètre donné et avec des paramètres donnés. La conséquence pratique est
directe : une politique qui « ne s'applique pas » vient presque toujours d'une
Constraint manquante ou d'un match qui ne correspond à rien.
Les deux CRD introduits sont donc :
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
Section intitulée « ConstraintTemplate »Un ConstraintTemplate fait deux choses en même temps, et c'est ce qui le rend
déroutant au premier abord. Le bloc crd.spec crée un nouveau type de
ressource Kubernetes (ici K8sRequiredRegistry) et décrit, via
openAPIV3Schema, les paramètres qu'il acceptera. Le bloc targets.rego
contient la logique qui sera évaluée, avec les paramètres exposés sous
input.parameters. La règle doit s'appeler violation et produire des objets
{"msg": ...} : c'est le contrat attendu par Gatekeeper, un deny comme dans
Conftest ne serait jamais lu.
apiVersion: templates.gatekeeper.sh/v1kind: ConstraintTemplatemetadata: name: k8srequiredregistryspec: 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 not registre_autorise(c.image) msg := sprintf( "conteneur '%v' : image '%v' hors des registres autorisés", [c.name, c.image] ) }
registre_autorise(image) if { some prefixe in input.parameters.registres_autorises startswith(image, sprintf("%v/", [prefixe])) }kubectl apply -f constrainttemplate-image-registry.yaml
# Vérifiez que le CRD est bien créékubectl get constrainttemplate k8srequiredregistryConstraint
Section intitulée « Constraint »La Constraint est une instance du type créé par le template. Elle répond à
deux questions que la logique Rego ne doit pas connaître : sur quoi la règle
s'applique (bloc match, par type de ressource et par namespace) et avec
quelles valeurs (bloc parameters). Cette séparation est l'intérêt principal
de Gatekeeper : un même template sert à imposer des registres différents en
production et en préproduction, sans dupliquer une ligne de Rego. Le match
est aussi votre filet de sécurité : une contrainte sans restriction de
namespace s'appliquerait aux namespaces système et pourrait bloquer les
composants du cluster.
apiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sRequiredRegistrymetadata: name: registry-autorise-productionspec: match: kinds: - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet", "DaemonSet"] namespaces: ["production", "staging"] parameters: registres_autorises: - "registry.example.com" - "gcr.io/mon-projet"kubectl apply -f constraint-image-registry-prod.yamlTestez la contrainte en appliquant un Deployment avec une image non conforme :
kubectl create deployment test-nginx \ --image=nginx:latest \ --namespace=production \ --dry-run=server
# Résultat attendu :# error: failed to create deployment: admission webhook "validation.gatekeeper.sh"# denied the request: [registry-autorise-production] conteneur 'nginx' :# image 'nginx:latest' hors des registres autorisésDeux détails comptent ici. La commande crée un Deployment, pas un Pod : la
contrainte cible apps/Deployment, un kubectl run produirait un Pod que le
bloc match ignorerait, et vous concluriez à tort que la politique ne
fonctionne pas. Le --dry-run=server envoie bien la requête à l'API server, qui
appelle le webhook, mais n'écrit rien dans etcd : c'est la façon la plus sûre de
valider une contrainte en production.
Mode audit
Section intitulée « Mode audit »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# Voir les violations détectées en mode auditkubectl get k8srequiredregistry registry-autorise-production -o json \ | jq '.status.violations'Récapitulatif : quel outil pour quel besoin
Section intitulée « Récapitulatif : quel outil pour quel besoin »Ces six outils partagent le même langage mais interviennent à des moments différents du cycle de vie, et c'est le seul critère de choix qui compte. Les deux premières lignes concernent votre poste, la troisième la chaîne d'intégration, la quatrième le cluster en fonctionnement. Un même corpus de règles gagne à couvrir au moins deux de ces moments : ce qui est refusé en CI doit rester refusé à l'admission, sinon un déploiement fait à la main contourne toute la démarche.
| Besoin | Outil |
|---|---|
| Tester une expression Rego localement | opa eval |
| Tests unitaires automatisés | opa test |
| Valider des manifests / IaC en CI/CD | Conftest |
| Contrôle d'admission Kubernetes | Gatekeeper |
| Autorisation microservices / API | OPA server (API REST) |
| Distribution centralisée de politiques | OPA Bundles |
À retenir
Section intitulée « À retenir »opa eval --explain fullest le premier outil de débogage quand une règle retourneundefined.- Les tests unitaires Rego utilisent
with input as fixture, pas de mock, pas de stub, juste de la substitution d'input. - Conftest utilise
deny/warndans le namespacemainet 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: dryrunen production, auditez avant de bloquer.