Exercice 04 : Open Policy Agent : compliance as code avec Rego
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Écrire des règles Rego pour valider une politique de sécurité
- ✅ Tester des règles Rego avec l'outil
opa eval - ✅ Installer OPA Gatekeeper sur un cluster Kubernetes
- ✅ Créer des
ConstraintTemplateetConstraintGatekeeper - ✅ Bloquer les déploiements sans
securityContextou sans resource limits
Durée estimée : 45 minutes
Difficulté : ⭐⭐⭐☆☆ (Avancé)
Prérequis : Cluster Kubernetes (minikube), kubectl, notions YAML K8s
📖 Contexte
OPA (Open Policy Agent) est un moteur de règles génériques. Il reçoit des données (un manifeste Kubernetes, une requête API, une config Terraform) et évalue si elles respectent des règles écrites en Rego, son langage déclaratif.
Gatekeeper est l'intégration officielle d'OPA dans Kubernetes. Il s'intercale dans le flux d'admission des ressources : quand vous faites kubectl apply, Gatekeeper évalue les règles avant d'accepter ou de refuser la ressource.
📋 Énoncé
Écrivez des politiques de sécurité avec Rego et intégrez-les dans Kubernetes via Gatekeeper.
🧭 Déroulement de l'exercice
Tâche 1 : Installer OPA CLI et tester Rego
# Installer OPA CLI
curl -sSL -o /tmp/opa https://github.com/open-policy-agent/opa/releases/latest/download/opa_linux_amd64_static
chmod +x /tmp/opa && sudo mv /tmp/opa /usr/local/bin/opa
opa versionCréez votre première règle Rego :
mkdir opa-demo && cd opa-demo
# Règle : interdire les containers qui tournent en root
cat > no-root-policy.rego << 'EOF'
package kubernetes.admission
# La règle "deny" est évaluée pour chaque ressource admise
# Si elle retourne un message, la ressource est rejetée
deny[msg] {
# S'applique uniquement aux Pods
input.request.kind.kind == "Pod"
# Pour chaque container dans la spec
container := input.request.object.spec.containers[_]
# Vérifier si runAsNonRoot n'est PAS défini à true
not container.securityContext.runAsNonRoot == true
# Message d'erreur retourné à l'utilisateur
msg := sprintf(
"Container '%v' doit avoir securityContext.runAsNonRoot: true",
[container.name]
)
}
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
# Vérifier que des resource limits sont définies
not container.resources.limits.cpu
msg := sprintf(
"Container '%v' doit définir resources.limits.cpu",
[container.name]
)
}
EOFTâche 2 : Tester les règles Rego localement
Créez des inputs de test (manifestes JSON) :
# Pod NON conforme (sans securityContext, sans resources)
cat > bad-pod.json << 'EOF'
{
"request": {
"kind": {"kind": "Pod"},
"object": {
"metadata": {"name": "bad-pod"},
"spec": {
"containers": [
{
"name": "app",
"image": "nginx:latest"
}
]
}
}
}
}
EOF
# Pod conforme
cat > good-pod.json << 'EOF'
{
"request": {
"kind": {"kind": "Pod"},
"object": {
"metadata": {"name": "good-pod"},
"spec": {
"containers": [
{
"name": "app",
"image": "nginx:1.27-alpine",
"securityContext": {
"runAsNonRoot": true,
"runAsUser": 1000,
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true
},
"resources": {
"limits": {"cpu": "100m", "memory": "128Mi"},
"requests": {"cpu": "50m", "memory": "64Mi"}
}
}
]
}
}
}
}
EOF
# Tester le pod non conforme (doit retourner des violations)
echo "=== Test pod non conforme ==="
opa eval --data no-root-policy.rego --input bad-pod.json "data.kubernetes.admission.deny"
# Tester le pod conforme (doit retourner un tableau vide)
echo "=== Test pod conforme ==="
opa eval --data no-root-policy.rego --input good-pod.json "data.kubernetes.admission.deny"Indice :
opa evalretourne le résultat de l'évaluation. Pour le pod non conforme, vous devriez voir 2 messages dans le tableaudeny. Pour le pod conforme, le tableau doit être vide[].
Vérification : Le pod non conforme génère 2 messages de violation.
Tâche 3 : Installer Gatekeeper sur le cluster
# Installer Gatekeeper (nécessite un cluster Kubernetes)
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.17/deploy/gatekeeper.yaml
# Attendre que Gatekeeper soit prêt
kubectl wait --for=condition=available deployment/gatekeeper-controller-manager \
-n gatekeeper-system --timeout=120s
kubectl get pods -n gatekeeper-systemTâche 4 : Créer une ConstraintTemplate
Une ConstraintTemplate définit la règle Rego et crée un nouveau type Kubernetes :
cat > constraint-template-runasnonroot.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequirerunasnonroot
spec:
crd:
spec:
names:
kind: K8sRequireRunAsNonRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequirerunasnonroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot == true
msg := sprintf(
"Container '%v' : securityContext.runAsNonRoot doit être true",
[container.name]
)
}
# Vérifier aussi les initContainers
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not container.securityContext.runAsNonRoot == true
msg := sprintf(
"InitContainer '%v' : securityContext.runAsNonRoot doit être true",
[container.name]
)
}
EOF
kubectl apply -f constraint-template-runasnonroot.yamlTâche 5 : Créer et tester des Constraints
Une Constraint instancie la règle sur des namespaces spécifiques :
# Activer la règle sur le namespace "production"
cat > constraint-runasnonroot.yaml << 'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireRunAsNonRoot
metadata:
name: require-run-as-non-root
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["production"] # S'applique uniquement à "production"
enforcementAction: deny # deny | warn | dryrun
EOF
kubectl apply -f constraint-runasnonroot.yaml
# Créer le namespace production
kubectl create namespace production
# Tenter de créer un pod non conforme (doit échouer)
kubectl run bad-pod --image=nginx:latest -n production
# → Error: container 'bad-pod' : securityContext.runAsNonRoot doit être true
# Créer un pod conforme (doit réussir)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: good-pod
namespace: production
spec:
containers:
- name: app
image: nginx:1.27-alpine
securityContext:
runAsNonRoot: true
runAsUser: 101
allowPrivilegeEscalation: false
EOFVérification : Le pod bad-pod est refusé, le pod good-pod est accepté.
✅ Vérification du résultat
opa evalretourne 2 violations pourbad-pod.jsonopa evalretourne 0 violations pourgood-pod.json- Gatekeeper est installé et ses pods sont Running
kubectl run bad-pod --image=nginx -n productionest bloqué par Gatekeeper- Le pod conforme avec
securityContext.runAsNonRoot: trueest accepté
💡 À retenir
OPA et Gatekeeper implémentent le compliance as code :
Développeur : kubectl apply
↓
Gatekeeper (Admission Webhook)
↓
OPA évalue les règles Rego
↓
Accepté ✅ ou Rejeté ❌ avec message d'erreurLes règles Gatekeeper remplacent les vérifications manuelles dans les revues de code. Elles s'appliquent automatiquement à chaque déploiement.
✨ Solution Complète
# Règle Rego : interdire les containers root
package k8snoroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot == true
msg := sprintf("Container '%v' doit avoir runAsNonRoot: true", [container.name])
}# Tester localement avant de déployer
opa eval --data ma-regle.rego --input mon-pod.json "data.k8snoroot.violation"