#!/bin/bash # Partie 1 - Installation de Kyverno + policies d'admission # À exécuter sur le nœud MASTER # # Kyverno = admission controller déclaratif (policies YAML, intercepte l'API server) # Agit AVANT le démarrage du pod (vs KubeArmor qui agit pendant l'exécution). # Les deux couches sont complémentaires : # Kyverno → bloque la création de workloads dangereux # KubeArmor → bloque les actions dangereuses dans les workloads qui tournent set -e KYVERNO_VERSION="3.4.1" echo "=== Installation Kyverno ${KYVERNO_VERSION} ===" echo "" # --- Installation via Helm --- echo "Ajout du repo Helm Kyverno..." helm repo add kyverno https://kyverno.github.io/kyverno/ 2>/dev/null || true helm repo update echo "" echo "Installation de Kyverno ${KYVERNO_VERSION}..." helm upgrade --install kyverno kyverno/kyverno \ --namespace kyverno \ --create-namespace \ --version "${KYVERNO_VERSION}" \ --set replicaCount=1 \ --set features.policyExceptions.enabled=true \ --wait --timeout=5m echo "" echo "Attente que Kyverno soit opérationnel..." kubectl wait --for=condition=ready pod \ -l app.kubernetes.io/name=kyverno \ -n kyverno \ --timeout=120s echo " ✓ Kyverno opérationnel" echo "" echo "Application des 8 ClusterPolicies de sécurité..." # Policy 1 : Interdire les containers privileged # POURQUOI: Un container privileged a accès complet au kernel de l'hôte (comme root sur l'hôte). # C'est le vecteur d'escalade le plus courant — un container privileged peut lire # /dev/kmem, charger des modules kernel, modifier les tables iptables de l'hôte. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged-containers annotations: policies.kyverno.io/description: "Interdit les containers privileged." spec: validationFailureAction: Enforce background: true rules: - name: deny-privileged match: any: - resources: kinds: [Pod] validate: message: "Les containers privileged sont interdits." pattern: spec: containers: - =(securityContext): =(privileged): "false" =(initContainers): - =(securityContext): =(privileged): "false" EOF # Policy 2 : Interdire hostNetwork, hostPID, hostIPC # POURQUOI: # hostNetwork : accès direct aux interfaces réseau de l'hôte (bypass complet du CNI) # hostPID : accès à tous les processus de l'hôte (lecture mémoire via /proc//mem) # hostIPC : accès aux segments de mémoire partagée de l'hôte kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-host-namespaces annotations: policies.kyverno.io/description: "Interdit hostNetwork, hostPID, hostIPC." spec: validationFailureAction: Enforce background: true rules: - name: deny-host-namespaces match: any: - resources: kinds: [Pod] validate: message: "hostNetwork, hostPID et hostIPC sont interdits." pattern: spec: =(hostNetwork): "false" =(hostPID): "false" =(hostIPC): "false" EOF # Policy 3 : Interdire les volumes hostPath # POURQUOI: hostPath monte un répertoire de l'hôte dans le container. # Vecteur classique : monter /etc pour modifier sudoers, /var/lib/kubelet pour # lire les tokens de ServiceAccount, ou / pour accès complet au système de fichiers hôte. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-hostpath-volumes annotations: policies.kyverno.io/description: "Interdit les volumes hostPath." spec: validationFailureAction: Enforce background: true rules: - name: deny-hostpath match: any: - resources: kinds: [Pod] validate: message: "Les volumes hostPath sont interdits." deny: conditions: any: - key: "{{ request.object.spec.volumes[].hostPath | length(@) }}" operator: GreaterThan value: 0 EOF # Policy 4 : Forcer les resource limits # POURQUOI: Sans limits, un pod compromis peut consommer tout le CPU/RAM du nœud # et provoquer un déni de service sur les autres pods (DoS interne). kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-resource-limits annotations: policies.kyverno.io/description: "Oblige à définir des limits CPU et mémoire." spec: validationFailureAction: Enforce background: true rules: - name: require-limits match: any: - resources: kinds: [Pod] namespaces: - "external-app" validate: message: "Les limits CPU et mémoire sont obligatoires." pattern: spec: containers: - resources: limits: cpu: "?*" memory: "?*" EOF # Policy 5 : Forcer runAsNonRoot # POURQUOI: Un container tournant en root (UID 0) peut, en cas de breakout, opérer # en root sur l'hôte si les protections kernel (SELinux, seccomp) sont contournées. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-run-as-non-root annotations: policies.kyverno.io/description: "Oblige les containers à tourner en non-root." spec: validationFailureAction: Enforce background: true rules: - name: require-non-root match: any: - resources: kinds: [Pod] namespaces: - "external-app" validate: message: "Les containers doivent tourner en non-root (runAsNonRoot: true)." pattern: spec: =(securityContext): =(runAsNonRoot): true containers: - =(securityContext): =(runAsNonRoot): true EOF # Policy 6 : Forcer readOnlyRootFilesystem # POURQUOI: Un FS racine en lecture seule empêche l'attaquant d'écrire des binaires, # de modifier des scripts de démarrage ou de persister dans le container. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-readonly-rootfs annotations: policies.kyverno.io/description: "Oblige le filesystem racine des containers à être en lecture seule." spec: validationFailureAction: Enforce background: true rules: - name: require-readonly-fs match: any: - resources: kinds: [Pod] namespaces: - "external-app" validate: message: "readOnlyRootFilesystem doit être true." pattern: spec: containers: - securityContext: readOnlyRootFilesystem: true EOF # Policy 7 : Interdire le tag :latest sur les images # POURQUOI: :latest est non-reproductible et peut changer silencieusement. # Permet aussi l'injection d'une image malveillante dans le registry. # Forcer les tags immuables (sha256 digest ou version sémantique) garantit # que le code déployé est bien celui qui a été validé. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-latest-tag annotations: policies.kyverno.io/description: "Interdit l'utilisation du tag :latest." spec: validationFailureAction: Enforce background: true rules: - name: deny-latest match: any: - resources: kinds: [Pod] validate: message: "Le tag ':latest' est interdit. Utiliser un tag de version ou un digest sha256." foreach: - list: "request.object.spec.containers" deny: conditions: any: - key: "{{ element.image }}" operator: Equals value: "*:latest" - key: "{{ element.image }}" operator: NotIn value: ["*:*"] EOF # Policy 8 : Générer automatiquement une NetworkPolicy deny-all dans chaque nouveau namespace # POURQUOI: Tout namespace sans NetworkPolicy est un réseau ouvert — tous les pods # peuvent se parler librement, y compris cross-namespace. # Kyverno génère la policy au moment de la création du namespace, # garantissant qu'aucun namespace n'est créé sans isolation réseau. kubectl apply -f - <<'EOF' apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: generate-default-deny-networkpolicy annotations: policies.kyverno.io/description: "Génère une NetworkPolicy deny-all dans chaque nouveau namespace." spec: rules: - name: generate-deny-all match: any: - resources: kinds: [Namespace] exclude: any: - resources: names: - kube-system - kube-public - kube-node-lease - kubearmor - kyverno - cilium-system generate: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy name: default-deny-all namespace: "{{request.object.metadata.name}}" synchronize: true data: spec: podSelector: {} policyTypes: - Ingress - Egress EOF echo " ✓ 8 ClusterPolicies appliquées" # --- Vérifications --- echo "" echo "=== Vérifications ===" echo "" echo "1. Pods Kyverno:" kubectl get pods -n kyverno echo "" echo "2. ClusterPolicies actives:" kubectl get clusterpolicies echo "" echo "3. Test de la policy 1 (pod privileged doit être refusé):" if kubectl run kyverno-test-privileged \ --image=nginx:1.25 \ --restart=Never \ --overrides='{"spec":{"containers":[{"name":"kyverno-test-privileged","image":"nginx:1.25","securityContext":{"privileged":true}}]}}' \ --dry-run=server 2>&1 | grep -q "disallow-privileged"; then echo " ✓ Pod privileged correctement refusé par Kyverno" else kubectl run kyverno-test-privileged \ --image=nginx:1.25 \ --restart=Never \ --overrides='{"spec":{"containers":[{"name":"kyverno-test-privileged","image":"nginx:1.25","securityContext":{"privileged":true}}]}}' \ --dry-run=server 2>&1 || echo " ✓ Pod privileged refusé (vérifier le message ci-dessus)" fi echo "" echo "✓ Kyverno installé avec succès!" echo "" echo "Voir les violations Kyverno :" echo " kubectl get policyreport --all-namespaces" echo " kubectl get clusterpolicyreport"