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-1

Slug : 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 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 : PRConftest ; 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

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

← Catalogue Tutoriels

Pour aller plus loin — hubs live

Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.