Les deux guides précédents permettent d’écrire des politiques fonctionnelles. Dès qu’un projet dépasse une poignée de fichiers, la duplication devient un problème : la même vérification d’image, les mêmes helpers de securityContext, réécrits dans chaque politique.
Rego dispose de trois mécanismes pour factoriser la logique : les règles partielles (incremental rules), les fonctions paramétrées, et la clause else. Ce guide explique quand utiliser chacun.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Écrire des règles partielles pour agréger messages ou valeurs
- Construire des chaînes de priorité avec
elsesans imbrication - Définir des fonctions paramétrées pour factoriser la logique métier
- Utiliser les fonctions built-in utiles en DevSecOps (chaînes, regex, JSON)
- Structurer une bibliothèque de helpers Kubernetes réutilisable
Prérequis
Section intitulée « Prérequis »Règles complètes vs règles partielles
Section intitulée « Règles complètes vs règles partielles »Règle complète (rappel)
Section intitulée « Règle complète (rappel) »Une règle complète a exactement une valeur pour un nom donné. Si plusieurs corps portent le même nom, OPA lève une erreur — sauf si c’est intentionnel (règle partielle).
# Règle complète — une seule définition possibleniveau_criticite := "haute" if { input.metadata.labels.env == "production"}Règles partielles (incremental rules)
Section intitulée « Règles partielles (incremental rules) »Une règle partielle peut avoir plusieurs corps portant le même nom. OPA les évalue tous et agrège les résultats :
- pour un ensemble : union de toutes les valeurs contribuées ;
- pour un objet : union de toutes les paires clé/valeur ;
- pour un booléen : vrai si au moins un corps est vrai (OR implicite).
C’est le mécanisme le plus puissant de Rego pour les politiques d’admission.
# Chaque bloc `violations contains msg` contribue des messages au même ensembleviolations contains msg if { some c in input.spec.template.spec.containers endswith(c.image, ":latest") msg := sprintf("conteneur '%v' : tag latest interdit", [c.name])}
violations contains msg if { some c in input.spec.template.spec.containers c.securityContext.privileged == true msg := sprintf("conteneur '%v' : mode privileged interdit", [c.name])}
violations contains msg if { not input.metadata.labels.app msg := "label 'app' manquant dans metadata.labels"}Chaque bloc est indépendant : ajouter une nouvelle vérification ne modifie aucun bloc existant. C’est le pattern ouvert/fermé appliqué aux politiques.
Règles partielles booléennes
Section intitulée « Règles partielles booléennes »Quand la valeur n’est pas un ensemble mais un booléen, plusieurs corps pour le même nom forment un OU :
# `image_suspecte` est vraie si l'une OU l'autre condition est vraieimage_suspecte if { endswith(input.image, ":latest") }image_suspecte if { not contains(input.image, ":") }image_suspecte if { startswith(input.image, "docker.io/") }C’est équivalent à un || dans un langage impératif, mais chaque cas est isolé dans son propre bloc, ce qui facilite la lecture et les tests unitaires.
La clause else
Section intitulée « La clause else »else définit une valeur de repli ordonnée pour une règle. Là où default est le filet global (évalué en dernier, sans condition), else permet d’enchaîner des conditions avec priorité explicite.
# Calcule le niveau de risque selon le contexteniveau_risque := "critique" if { input.metadata.namespace == "production" image_suspecte} else := "eleve" if { input.metadata.namespace == "production"} else := "moyen" if { image_suspecte} else := "faible"OPA évalue les clauses dans l’ordre et s’arrête à la première vraie. C’est l’équivalent propre d’un if / else if / else sans imbrication.
Fonctions paramétrées
Section intitulée « Fonctions paramétrées »Une fonction Rego est une règle qui prend des arguments et retourne une valeur. Elle se distingue d’une règle normale par la présence de paramètres entre parenthèses.
# Syntaxe : nom(param1, param2, ...) := valeur_retour if { corps }image_conforme(image) if { startswith(image, "registry.example.com/") contains(image, ":") not endswith(image, ":latest")}Appel :
deny contains msg if { some c in input.spec.template.spec.containers not image_conforme(c.image) msg := sprintf("conteneur '%v' : image non conforme", [c.name])}Fonctions qui retournent une valeur
Section intitulée « Fonctions qui retournent une valeur »Une fonction peut retourner n’importe quel type, pas seulement un booléen.
# Retourne le registre d'une image (tout ce qui précède le premier `/`)registre(image) := reg if { parties := split(image, "/") reg := parties[0]}
# Retourne "inconnu" si l'image ne contient pas de `/`registre(image) := "inconnu" if { not contains(image, "/")}Fonctions récursives
Section intitulée « Fonctions récursives »Rego interdit la récursion directe pour garantir la terminaison de l’évaluation. Si vous avez besoin de traverser une structure arborescente, les compréhensions imbriquées ou les fonctions built-in sont la solution.
Fonctions built-in utiles
Section intitulée « Fonctions built-in utiles »OPA embarque une bibliothèque étendue. Voici les plus utiles en contexte DevSecOps.
startswith("registry.example.com/api:1.0", "registry.example.com/") # trueendswith("api:latest", ":latest") # truecontains("apps/v1", "/") # truesplit("registry.example.com/api:1.0", "/") # ["registry.example.com", "api:1.0"]concat(", ", ["a", "b", "c"]) # "a, b, c"trim_space(" valeur ") # "valeur"upper("production") # "PRODUCTION"sprintf("conteneur '%v' sur port %v", ["nginx", 80]) # "conteneur 'nginx' sur port 80"Expressions régulières
Section intitulée « Expressions régulières »regex.match(`^[a-z0-9-]+$`, input.metadata.name) # nom DNS valideregex.match(`^registry\.(example|interne)\.com/`, img) # registre autoriséJSON et données structurées
Section intitulée « JSON et données structurées »# Décoder une annotation stockée comme chaîne JSONannotation := input.metadata.annotations["config.json"]config := json.unmarshal(annotation)config.replicas > 0
# Encoderpayload := json.marshal({"key": "value"})Vérification de types
Section intitulée « Vérification de types »is_string(input.metadata.name) # true si c'est une stringis_number(input.spec.replicas) # true si c'est un nombreis_array(input.spec.containers) # true si c'est un tableauFil rouge : bibliothèque de helpers Kubernetes
Section intitulée « Fil rouge : bibliothèque de helpers Kubernetes »On extrait toute la logique métier dans un fichier lib/kubernetes.rego réutilisable. Les politiques n’y font plus que des appels de fonctions.
package lib.kubernetes
import rego.v1
# Retourne true si le conteneur tourne en mode privilegedest_privilege(conteneur) if { conteneur.securityContext.privileged == true}
# Retourne true si l'image ne respecte pas la politique de registreimage_non_conforme(image) if { not startswith(image, "registry.example.com/")}
image_non_conforme(image) if { endswith(image, ":latest")}
image_non_conforme(image) if { not contains(image, ":")}
# Retourne le nom de tous les conteneurs (init + principaux)tous_les_conteneurs(spec) := conteneurs if { principaux := spec.containers inits := object.get(spec, "initContainers", []) conteneurs := array.concat(principaux, inits)}
# Retourne true si un label obligatoire est absentlabel_manquant(labels, nom) if { not labels[nom]}La politique d’admission importe et utilise cette bibliothèque :
package kubernetes.admission.deployment
import rego.v1import data.lib.kubernetes
default allow := false
allow if { count(violations) == 0}
violations contains msg if { some c in kubernetes.tous_les_conteneurs(input.spec.template.spec) kubernetes.image_non_conforme(c.image) msg := sprintf("conteneur '%v' : image '%v' non conforme", [c.name, c.image])}
violations contains msg if { some c in kubernetes.tous_les_conteneurs(input.spec.template.spec) kubernetes.est_privilege(c) msg := sprintf("conteneur '%v' : mode privileged interdit", [c.name])}
violations contains msg if { labels_requis := ["app", "team", "env"] some label in labels_requis kubernetes.label_manquant(input.metadata.labels, label) msg := sprintf("label obligatoire manquant : '%v'", [label])}L’évaluation charge les deux fichiers :
opa eval \ --input manifest.json \ --data lib/ \ --data policies/ \ "data.kubernetes.admission.deployment.violations"Ajouter une nouvelle vérification se résume à un bloc violations contains msg supplémentaire dans la politique, sans toucher à la bibliothèque. Ajouter une règle réutilisable ne touche pas aux politiques existantes.
Choisir entre règle partielle, fonction et règle complète
Section intitulée « Choisir entre règle partielle, fonction et règle complète »| Besoin | Forme recommandée |
|---|---|
| Agréger des messages d’erreur | Règle partielle (violations contains msg) |
| Logique booléenne multi-cas (OU) | Règle partielle booléenne |
| Logique réutilisable avec paramètres | Fonction |
| Logique de priorité ordonnée | Règle avec else |
| Valeur par défaut globale | default |
| Valeur calculée unique | Règle complète |
À retenir
Section intitulée « À retenir »- Les règles partielles permettent le OU et l’agrégation : plusieurs corps, un résultat fusionné.
elseexprime une priorité ordonnée ;defaultest le filet global non conditionnel.- Les fonctions paramétrées extraient la logique métier des politiques et la rendent testable isolément.
- Le pattern bibliothèque (
data.lib.*) est la façon standard de partager du code Rego entre plusieurs politiques. - Rego interdit la récursion — concevez vos fonctions comme des transformations pures sur des collections.