|8 min de lecture|Yvann Lièvre

Policy-as-code : sécuriser Kubernetes et l'IaC avec OPA/Rego et Kyverno

Le policy as code OPA Rego Kubernetes permet de refuser un pod privilégié avant qu'il ne démarre. OPA/Rego vs Kyverno, exemples, tests, et preuve NIS2/DORA.

Policy as CodeKubernetesOPAConformité
Policy-as-code : sécuriser Kubernetes et l'IaC avec OPA/Rego et Kyverno

Un pod tourne en privileged: true en production. Personne ne l'a décidé explicitement : un manifeste copié d'un tutoriel, un Dockerfile qui n'a jamais été revu, une image tirée d'un registre public sans vérification de signature. Le cluster l'a accepté parce que rien, techniquement, ne l'en empêchait. C'est exactement le problème que résout le policy as code : transformer une règle écrite dans un document de sécurité en barrière exécutée par la plateforme elle-même, au moment où l'objet est créé, pas après coup dans un rapport d'audit.

L'admission control, concrètement

Kubernetes expose un point d'ancrage précis pour ce contrôle : l'admission webhook. Chaque requête de création ou de modification d'objet (un pod, un déploiement, une règle réseau) transite par une chaîne de validation avant d'être persistée dans etcd. Un moteur de policy branché à cet endroit peut refuser la requête avant même que le pod ne démarre.

Deux cas concrets, fréquents en audit :

  • Un pod demande securityContext.privileged: true ou monte le socket Docker de l'hôte : sans policy, il démarre normalement et dispose d'un accès quasi root sur le nœud.
  • Une image provient d'un registre non vérifié ou n'est pas signée (Cosign, Notary) : sans policy, Kubernetes ne fait aucune différence entre une image signée par la chaîne CI officielle et une image poussée manuellement par n'importe qui disposant des identifiants du registre.

Dans les deux cas, l'admission control transforme un incident potentiel en un refus silencieux et journalisé, avant impact.

OPA/Rego ou Kyverno : deux philosophies

Deux moteurs dominent ce terrain, avec des logiques différentes.

OPA (Open Policy Agent) et son langage Rego sont généralistes. Le même moteur peut valider un manifeste Kubernetes, un plan Terraform avant apply, une règle de routage Envoy, ou une étape de pipeline CI/CD. Rego est un langage de requête logique à part entière, avec sa propre courbe d'apprentissage : il faut penser en termes de faits et de règles, pas en YAML séquentiel. C'est la contrepartie de sa versatilité multi-stack.

Kyverno est natif Kubernetes et s'écrit en YAML pur, sans langage tiers à apprendre. Une équipe qui maîtrise déjà les manifestes Kubernetes écrit une policy Kyverno en quelques minutes. La limite est le périmètre : Kyverno ne sort pas de l'écosystème Kubernetes, il ne validera pas un plan Terraform ou une règle Envoy.

En pratique, l'approche hybride est la plus courante en environnement mature : Kyverno pour les policies purement Kubernetes à fort volume et faible complexité (labels obligatoires, restrictions de securityContext, quotas), OPA/Rego pour tout ce qui traverse plusieurs stacks (IaC, API gateway, pipelines) ou nécessite une logique de décision plus fine. Le mauvais réflexe est de choisir un moteur unique par dogme plutôt que par périmètre réel.

Anatomie d'une policy Rego

Une policy Rego s'organise en package (le namespace logique) et en règles. La règle deny est la convention la plus répandue pour l'admission control : chaque instance de deny qui s'évalue à vrai bloque la requête et renvoie le message associé.

package kubernetes.admission
 
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    container.securityContext.privileged == true
    msg := sprintf(
        "Le conteneur '%v' du pod '%v' est privilegie : refuse par la policy.",
        [container.name, input.request.object.metadata.name],
    )
}

La structure est lisible : package isole la policy, la règle deny décompose la condition (type d'objet, itération sur les conteneurs, vérification du champ), et msg construit le message renvoyé à l'utilisateur qui tente le déploiement. C'est ce message qui apparaît dans le retour kubectl apply, pas une erreur générique.

L'équivalent Kyverno pour la même règle tient en YAML déclaratif, sans logique de requête à écrire :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
spec:
  validationFailureAction: Enforce
  rules:
    - name: block-privileged
      match:
        resources:
          kinds: ["Pod"]
      validate:
        message: "Les conteneurs privilegies sont interdits."
        pattern:
          spec:
            containers:
              - =(securityContext):
                  =(privileged): "false"

Même intention, deux syntaxes. Le choix dépend du périmètre à couvrir, pas de la préférence stylistique de l'équipe.

Tester avant de bloquer un déploiement critique

Une policy mal écrite est aussi dangereuse qu'une absence de policy : elle peut bloquer un déploiement légitime en pleine astreinte, ou au contraire laisser passer un cas qu'elle croyait couvrir. Deux outils évitent cette double peine.

opa test exécute des tests unitaires écrits en Rego contre la policy, avant tout déploiement en admission controller :

opa test policies/ -v
package kubernetes.admission
 
test_privileged_pod_denied {
    deny["Le conteneur 'app' du pod 'demo' est privilegie : refuse par la policy."] with input as {
        "request": {
            "kind": {"kind": "Pod"},
            "object": {
                "metadata": {"name": "demo"},
                "spec": {"containers": [{"name": "app", "securityContext": {"privileged": true}}]},
            },
        },
    }
}

conftest applique le même principe directement sur des fichiers de manifestes ou des plans Terraform exportés en JSON, ce qui permet de tester la policy en pipeline CI/CD avant même qu'elle n'atteigne le cluster :

conftest test --policy policies/ deployment.yaml

L'ordre de déploiement recommandé reste constant : opa test pour valider la logique de la règle elle-même, conftest test en CI pour valider un large corpus de manifestes réels sans régression, et seulement ensuite l'activation en mode enforcement dans le cluster. Sauter cette étape, c'est le scénario classique de l'astreinte réveillée à 2h du matin par un déploiement bloqué pour une raison qui n'a jamais été testée.

De la policy technique à la preuve de conformité

NIS2 et DORA exigent des contrôles techniques documentés et, de plus en plus, des preuves d'automatisation plutôt que des procédures manuelles déclaratives. Le policy as code fournit un mapping direct entre une exigence réglementaire et une règle exécutée :

Exigence NIS2/DORAPolicy technique correspondante
Chiffrement des données au repos et en transitRego/Kyverno refusant un PVC sans chiffrement, ou un service sans TLS
Segmentation réseauNetworkPolicy Kubernetes générée et validée par policy, empêchant le trafic pod-to-pod non autorisé
Gestion des accès (moindre privilège)Règles RBAC auditées automatiquement, refus des ClusterRoleBinding trop larges
Intégrité de la chaîne logicielleRefus d'admission des images non signées

Ce mapping n'est pas cosmétique : dans un audit NIS2 ou DORA, prouver qu'un contrôle est appliqué automatiquement et systématiquement (et pas seulement documenté dans une procédure) est ce qui distingue une conformité auditée d'une conformité déclarative. Le journal de refus de l'admission controller devient une pièce de preuve directement exploitable.

En résumé

Le policy as code déplace le contrôle de sécurité du document vers l'exécution : une policy Rego ou Kyverno refuse la dérive avant qu'elle ne produise un incident, et non plusieurs semaines après dans un rapport d'audit. Le choix du moteur suit le périmètre (Kyverno pour du Kubernetes pur, OPA/Rego pour du multi-stack), et le passage en production ne se fait jamais sans tests unitaires préalables. Le mapping vers NIS2/DORA transforme ensuite chaque policy technique en élément de preuve de conformité automatisée.

C'est précisément ce que fournit le pack de policies ThreatClaw : des règles Rego et Kyverno pré-testées, prêtes pour l'admission control, avec le mapping de conformité déjà établi, pour éviter d'écrire et de valider chaque règle sous pression au moment de l'audit.

Articles liés