Signed-off-by: Louis Labeyrie <labeyrielouis@gmail.com>
This commit is contained in:
2026-04-27 15:29:07 +02:00
parent 5760eab56a
commit 1e24ec3243
10 changed files with 1501 additions and 125 deletions

View File

@@ -0,0 +1,340 @@
#!/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/<pid>/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"