Policy-as-Code avec OPA et Gatekeeper en production
À la fin de ce tutoriel, vous expliquerez le Policy-as-Code, écrirez du Rego minimal, installerez OPA Gatekeeper, déploierez ConstraintTemplate + Constraint (privileged / labels), testerez deny vs allow, et brancherez CI Conftest + admission prod — angle DEH, région
ca-central-1.Niveau : Intermédiaire · Temps : 55–75 min · Versions : K8s 1.31+ · Gatekeeper 3.17+ · OPA/Rego · kubectl · Vérifié : 2026-09-11 · Region :
ca-central-1Slug :
wow-policy-as-code-opa· Série : WOW (9/50) · Mot-clé SEO : policy as code opa · Publish : READY (feu vert Maître)← Précédent : Tests Terraform 2026 · → Suivant : Conftest CI · Aussi : Platform Engineering · PSA · RBAC
Prérequis
kubectl+ cluster lab (kind/k3d/minikube/EKS jetable) 1.31+, droits admin (CRD/webhook)- Bases YAML/Pods/Deployments — pods · Deployment
- Sécu K8s : PSA · Network Policies · RBAC
- Cluster jetable ; aucun secret réel dans Git
Coût : 0 € kind/k3d ; EKS ca-central-1 = control plane + nœuds — delete en fin de session.
kubectl version --client && kubectl cluster-info && kubectl get nodes
export AWS_DEFAULT_REGION=ca-central-1 # si lab EKS
Ce que nous allons construire
Lab OPA / Gatekeeper (WOW 9/50) — ca-central-1
├── Pourquoi Policy-as-Code · OPA vs Gatekeeper vs Conftest
├── Rego (input, package, deny) · Install Gatekeeper
├── ConstraintTemplate + Constraint (privileged / labels)
├── Tests deny/allow · CI Conftest + admission prod
└── Patterns prod · Vérif · Quiz · FAQ · Maillage
Étape 1 — Pourquoi Policy-as-Code en 2026 ?
Les runbooks PDF et règles Slack ne tiennent pas à l’échelle. Une policy versionnée, testée en CI et appliquée à l’admission devient un garde-fou produit — cœur du Platform Engineering.
| Pression 2026 | Sans PaC | Avec Gatekeeper |
|---|---|---|
| Privileged / hostPath | Drift | Deny admission |
| Labels Owner / Service | FinOps flou | Constraint |
| Multi-équipes | Tickets sécu | Règle unique |
| Audit | Preuves manuelles | Git + dry-run |
EKS ca-central-1 |
IAM seul | Admission + IRSA |
Policy-as-Code = intention sécu en Git, revue PR, exécution auto — pas un wiki.
Étape 2 — OPA vs Gatekeeper vs Conftest
OPA = moteur ; Rego = langage ; Gatekeeper = OPA + webhook/CRD K8s ; Conftest = même Rego hors cluster (YAML/JSON/plan TF).
| Outil | Où | Force | Limite |
|---|---|---|---|
| OPA | API / app / CI | Moteur unique | Intégration à brancher |
| Gatekeeper | Admission K8s | ConstraintTemplate/Constraint | Trop tard si YAML déjà mergé |
| Conftest | CI / local | Shift-left | Pas de runtime cluster |
| PSA / Kyverno | Native / alt. | Simple / YAML | Moins expressif que Rego maison |
DEH : Conftest CI + Gatekeeper prod + PSA.
Étape 3 — Concepts Rego (input, package, deny)
Rego lit un JSON input. Gatekeeper fournit l’objet sous input.review.object. Une règle deny / violation émet un message si la condition est vraie.
package deh.k8s.privileged
deny[msg] if {
c := input.review.object.spec.containers[_]
c.securityContext.privileged == true
msg := sprintf("container %v privileged — interdit DEH", [c.name])
}
package = namespace ; input = objet ; deny/violation = motif du refus. Style déclaratif : vous dites quand c’est interdit.
Étape 4 — Lab : installer Gatekeeper
Pinnez une release stable (ex. v3.17.1 — vérifiez le tag GitHub au lab) via manifests officiels :
GK_VERSION=v3.17.1
kubectl apply -f "https://raw.githubusercontent.com/open-policy-agent/gatekeeper/${GK_VERSION}/deploy/gatekeeper.yaml"
kubectl -n gatekeeper-system rollout status deploy/gatekeeper-controller-manager --timeout=180s
kubectl -n gatekeeper-system get pods
kubectl get validatingwebhookconfiguration | grep gatekeeper || true
Attendu : pods Ready + webhook Validating. Sur EKS ca-central-1, le webhook doit joindre l’API (Network Policies).
Prod : pin image/digest, ressources, PDB ; commencez en
enforcementAction: dryrun.
Étape 5 — ConstraintTemplate + Constraint
Deux garde-fous : pas de privileged, et labels app + owner (Deployment).
5.1 Template + Constraint : deny privileged
# ct-deny-privileged.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdenypprivileged
spec:
crd:
spec:
names:
kind: K8sDenyPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdenypprivileged
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
c.securityContext.privileged == true
msg := sprintf("container %v: privileged=true interdit (DEH)", [c.name])
}
violation[{"msg": msg}] {
c := input.review.object.spec.initContainers[_]
c.securityContext.privileged == true
msg := sprintf("initContainer %v: privileged=true interdit (DEH)", [c.name])
}
# constraint-deny-privileged.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyPrivileged
metadata:
name: deny-privileged-pods
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
5.2 Template + Constraint : labels app / owner
# ct-require-labels.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("labels manquants : %v", [missing])
}
# constraint-require-labels.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: must-have-app-owner
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
labels: ["app", "owner"]
kubectl apply -f ct-deny-privileged.yaml
kubectl apply -f constraint-deny-privileged.yaml
kubectl apply -f ct-require-labels.yaml
kubectl apply -f constraint-require-labels.yaml
kubectl get constrainttemplate,k8sdenypprivileged,k8srequiredlabels
Attendez le statut enforced avant les tests.
Étape 6 — Test deny vs allow avec kubectl
Deny privileged :
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: bad-privileged
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.9
securityContext:
privileged: true
YAML
# Attendu : Error ... privileged=true interdit
Allow non-privileged : même Pod sans privileged: true (ou privileged: false) → créé. Supprimez-le ensuite.
Deny Deployment sans labels :
kubectl apply -f - <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
name: missing-labels
spec:
replicas: 1
selector:
matchLabels: {app: demo}
template:
metadata:
labels: {app: demo}
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.9
YAML
# Attendu : labels manquants (owner)
Allow avec app + owner :
kubectl apply -f - <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
name: ok-labeled
labels:
app: demo
owner: platform-deh
spec:
replicas: 1
selector:
matchLabels: {app: demo}
template:
metadata:
labels: {app: demo, owner: platform-deh}
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.9
YAML
kubectl get deploy ok-labeled
Étape 7 — CI shift-left + admission en prod
Gatekeeper est trop tard si le YAML est déjà mergé. Enchaînez : PR → Conftest ; scan images → Trivy ; pipeline → DevSecOps ; sync → Argo CD ; admission Gatekeeper en prod. CI = fichiers ; cluster = input.review. Deux portes, une intention.
Étape 8 — Patterns prod + bonnes pratiques DEH
| Pattern | Pratique DEH |
|---|---|
| dryrun | enforcementAction: dryrun + métriques avant deny |
| Audit | Audit Gatekeeper ; tickets sur violations déjà présentes |
| Exceptions | excludedNamespaces (ex. kube-system / CNI) documentées |
| Ownership | Labels owner + revue PR des policies |
| Perf | Scope kinds + namespaces ; Rego ciblé |
| EKS | ca-central-1 ; IRSA ; IAM ≠ admission Pod |
Repo platform-policies en GitOps (Argo CD). Noms de Constraints lisibles. Pas de bypass permanent hors Git. Alignez tags AWS (Owner, Service) avec labels K8s. PSA restricted + Gatekeeper = couches complémentaires (PSA, RBAC).
Vérification fin de lab
kubectl -n gatekeeper-system get pods
kubectl get constrainttemplate,k8sdenypprivileged,k8srequiredlabels
kubectl run probe-priv --image=registry.k8s.io/pause:3.9 --privileged=true 2>&1 | grep -i privileg || true
kubectl get deploy ok-labeled
Checklist : Gatekeeper Ready · Constraints enforced · privileged refusé · Deployment sans owner refusé · labellisé accepté.
Nettoyage
kubectl delete deploy ok-labeled missing-labels --ignore-not-found
kubectl delete pod bad-privileged good-pause probe-priv --ignore-not-found
kubectl delete k8sdenypprivileged,k8srequiredlabels --all --ignore-not-found
kubectl delete constrainttemplate k8sdenypprivileged k8srequiredlabels --ignore-not-found
GK_VERSION=v3.17.1
kubectl delete -f "https://raw.githubusercontent.com/open-policy-agent/gatekeeper/${GK_VERSION}/deploy/gatekeeper.yaml" --ignore-not-found
Erreurs fréquentes
| Symptôme | Cause | Correction |
|---|---|---|
| Webhook timeout | API ↔ Gatekeeper | Réseau / NP / nœuds Ready |
| Constraint pas enforced | Rego invalide | describe constrainttemplate |
| Deny Pod seulement | match.kinds étroit |
Ajouter controllers si besoin |
| Privileged via init | Containers seuls | Couvrir initContainers |
| Labels template absents | Match metadata Deployment | Rego template.metadata / PSA |
| Perf API lente | Scope trop large | Limiter namespaces + kinds |
Quiz (5 questions)
1. OPA désigne surtout :
– A. Un CNI Kubernetes
– B. Un moteur de policy (Rego) réutilisable
– C. Un type d’instance EC2
2. Gatekeeper intervient principalement :
– A. Après que le Pod tourne depuis 1 h
– B. À l’admission (validating webhook) avant persistance
– C. Uniquement dans Terraform Cloud
3. Une ConstraintTemplate sert à :
– A. Remplacer etcd
– B. Définir le CRD Constraint + le Rego associé
– C. Facturer AWS
4. Pour shift-left avant le merge, DEH recommande :
– A. Attendre l’incident prod
– B. Conftest (Rego) dans le CI + Gatekeeper en cluster
– C. Désactiver le webhook
5. Region lab standard DEH :
– A. us-east-1
– B. ca-central-1
– C. eu-west-3
Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑B
FAQ
OPA et Gatekeeper, c’est la même chose ?
Non. OPA = moteur. Gatekeeper = CRD + webhook + audit sur Kubernetes.
Faut-il abandonner Pod Security Admission ?
Non. PSA (baseline/restricted) + Gatekeeper (règles métier) se complètent — PSA.
Rego est-il obligatoire ?
Non. Kyverno/PSA suffisent parfois. Rego brille pour logique expressive partagée avec Conftest.
Exceptions CNI / kube-system ?
excludedNamespaces documentés + revue périodique — pas de bypass opaque.
Gatekeeper remplace-t-il le RBAC ?
Non. RBAC = qui ; Gatekeeper = quoi. Les deux (RBAC).
Tester sans casser le cluster ?
dryrun, namespace lab, puis deny ; Conftest en local avant apply.
Terraform / AWS ca-central-1 ?
Admission ≠ IAM. IaC : Conftest / tests Terraform + tags région.
Quelle version pinner ?
Release Gatekeeper stable (ex. v3.17.x) + digest en prod, versionnée dans le repo policies.
Pour aller plus loin
- Conftest CI (WOW 10) · Tests Terraform 2026 · Platform Engineering
- PSA · Network Policies · RBAC
- Argo CD · Trivy · DevSecOps
- Upstream : Gatekeeper docs
Maillage série WOW
| ← Précédent | Tests Terraform 2026 |
| → Suivant | Conftest dans le CI |
| Aussi | Platform Engineering · PSA · Network Policies · RBAC · Argo CD · Trivy · DevSecOps |
Meta publication (SEO)
- Title SEO : Policy-as-Code avec OPA et Gatekeeper en production (guide FR)
- Meta description : Policy-as-Code OPA Gatekeeper : Rego, ConstraintTemplate, deny privileged, labels app/owner, lab K8s, dry-run et CI Conftest. Région ca-central-1 — DEH.
- Focus keyword : policy as code opa · Secondary : OPA Gatekeeper, ConstraintTemplate, Rego Kubernetes
- Image :
assets/web/devopelastichayway/cover-wow-policy-as-code-opa-1200x630.webp(à générer) - Cover alt : Gatekeeper refuse un Pod privileged via Rego
- Catégorie : WOW / Sécurité Kubernetes · Niveau : Intermédiaire · Schema : HowTo + FAQPage + Article
- URL cible : https://devopelastichayway.com/tutoriels/wow-policy-as-code-opa/
- Post live : N/A · slug
wow-policy-as-code-opa· Publish : READY (feu vert Maître — GO) · Region :ca-central-1
Pour aller plus loin — hubs live
Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.