#!/bin/bash # Partie 1 - Vérification complète du cluster sécurisé # À exécuter sur le nœud MASTER set -e PASS=0 FAIL=0 # Nettoyer les ressources de test même en cas d'échec CLEANUP_RESOURCES=() cleanup() { if [[ ${#CLEANUP_RESOURCES[@]} -gt 0 ]]; then echo "" echo "--- Nettoyage des ressources de test ---" for res in "${CLEANUP_RESOURCES[@]}"; do kubectl delete $res --ignore-not-found=true 2>/dev/null || true done fi } trap cleanup EXIT check_ok() { echo " ✓ $1"; ((PASS++)); } check_fail() { echo " ✗ $1"; ((FAIL++)); } echo "=== Vérification du cluster Kubernetes sécurisé ===" echo "" # --- 1. Nœuds --- echo "1. Nœuds du cluster:" kubectl get nodes -o wide echo "" # --- 2. Composants control plane --- echo "2. Composants control plane:" kubectl get pods -n kube-system echo "" # --- 3. Cilium --- echo "3. Cilium:" if cilium status 2>/dev/null | grep -q "OK"; then check_ok "Cilium opérationnel" else check_fail "Cilium non opérationnel" fi if kubectl get pods -n kube-system -l app.kubernetes.io/name=cilium 2>/dev/null | grep -q "Running"; then check_ok "Pods Cilium en état Running" else check_fail "Pods Cilium non Running" fi if kubectl get pods -n kube-system -l app.kubernetes.io/name=hubble-relay 2>/dev/null | grep -q "Running"; then check_ok "Hubble relay opérationnel" else check_fail "Hubble relay non opérationnel" fi if kubectl get pods -n kube-system -l app.kubernetes.io/name=tetragon 2>/dev/null | grep -q "Running"; then check_ok "Tetragon opérationnel" else check_fail "Tetragon non opérationnel" fi echo "" # --- 4. KubeArmor --- echo "4. KubeArmor:" if kubectl get pods -n kubearmor 2>/dev/null | grep -q "Running"; then check_ok "Pods KubeArmor en état Running" else check_fail "Pods KubeArmor non Running" fi KUBEARMOR_POLICIES=$(kubectl get clusterkubearmorpolicies --no-headers 2>/dev/null | wc -l || echo "0") if [[ "$KUBEARMOR_POLICIES" -ge 6 ]]; then check_ok "ClusterKubeArmorPolicies actives: ${KUBEARMOR_POLICIES}" else check_fail "ClusterKubeArmorPolicies insuffisantes (trouvées: ${KUBEARMOR_POLICIES}, attendues: 6)" fi echo "" # --- 5. Kyverno --- echo "5. Kyverno:" if kubectl get pods -n kyverno 2>/dev/null | grep -q "Running"; then check_ok "Pods Kyverno en état Running" else check_fail "Pods Kyverno non Running" fi KYVERNO_POLICIES=$(kubectl get clusterpolicies --no-headers 2>/dev/null | wc -l || echo "0") if [[ "$KYVERNO_POLICIES" -ge 14 ]]; then check_ok "ClusterPolicies Kyverno actives: ${KYVERNO_POLICIES}" else check_fail "ClusterPolicies Kyverno insuffisantes (trouvées: ${KYVERNO_POLICIES}, attendues: 14)" fi echo "" # --- 6. Audit logs --- echo "6. Audit logging:" AUDIT_LOG="/var/log/kubernetes/audit/audit.log" if sudo test -f "$AUDIT_LOG" 2>/dev/null; then AUDIT_LINES=$(sudo wc -l < "$AUDIT_LOG" 2>/dev/null || echo "0") check_ok "Audit log actif ($AUDIT_LINES entrées): $AUDIT_LOG" else check_fail "Audit log non trouvé: $AUDIT_LOG" fi echo "" # --- 7. Chiffrement etcd at-rest --- echo "7. Chiffrement etcd at-rest:" # Créer un secret test et vérifier qu'il est chiffré dans etcd kubectl create secret generic etcd-encryption-check \ --from-literal=key=valeur-test-chiffrement \ -n default \ --dry-run=client -o yaml | kubectl apply -f - > /dev/null 2>&1 CLEANUP_RESOURCES+=("secret/etcd-encryption-check -n default") ETCD_DATA=$(sudo ETCDCTL_API=3 etcdctl \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ get /registry/secrets/default/etcd-encryption-check 2>/dev/null || echo "") if echo "$ETCD_DATA" | grep -q "k8s:enc:aescbc"; then check_ok "Secrets chiffrés AES-CBC dans etcd" elif [[ -z "$ETCD_DATA" ]]; then check_fail "Impossible de lire etcd (vérifier les certificats)" else check_fail "Secrets NON chiffrés dans etcd (données en clair détectées)" fi echo "" # --- 8. Test NetworkPolicy deny-all --- echo "8. NetworkPolicy deny-all:" kubectl run netpol-test-sender \ --image=busybox:1.36 \ --restart=Never \ -- sleep 3600 2>/dev/null || true CLEANUP_RESOURCES+=("pod/netpol-test-sender -n default") kubectl run netpol-test-receiver \ --image=busybox:1.36 \ --restart=Never \ -- sleep 3600 2>/dev/null || true CLEANUP_RESOURCES+=("pod/netpol-test-receiver -n default") kubectl wait --for=condition=ready pod/netpol-test-sender pod/netpol-test-receiver \ --timeout=60s 2>/dev/null || true RECEIVER_IP=$(kubectl get pod netpol-test-receiver -o jsonpath='{.status.podIP}' 2>/dev/null || echo "") if [[ -n "$RECEIVER_IP" ]]; then if kubectl exec netpol-test-sender -- \ wget -qO- --timeout=3 "http://${RECEIVER_IP}" 2>/dev/null; then check_fail "NetworkPolicy deny-all NON effective (communication inter-pods possible)" else check_ok "NetworkPolicy deny-all effective (communication inter-pods bloquée)" fi else check_fail "Impossible de récupérer l'IP du pod receiver" fi echo "" # --- 9. Test Kyverno : pod privileged refusé --- echo "9. Kyverno - rejet pod privileged:" KYVERNO_REJECT_OUTPUT=$(kubectl run kyverno-priv-test \ --image=nginx:1.25 \ --restart=Never \ --overrides='{"spec":{"containers":[{"name":"kyverno-priv-test","image":"nginx:1.25","securityContext":{"privileged":true}}]}}' \ --dry-run=server 2>&1 || true) if echo "$KYVERNO_REJECT_OUTPUT" | grep -qiE "disallow-privileged|admission webhook|denied"; then check_ok "Pod privileged correctement refusé par Kyverno" else check_fail "Pod privileged NON refusé — vérifier la ClusterPolicy disallow-privileged-containers" fi echo "" # --- 10. Test de déploiement légitime --- echo "10. Test déploiement légitime (sans restrictions):" kubectl create deployment cluster-verify-test \ --image=nginx:1.25 \ --replicas=2 2>/dev/null || true CLEANUP_RESOURCES+=("deployment/cluster-verify-test -n default") kubectl wait --for=condition=available deployment/cluster-verify-test \ --timeout=60s 2>/dev/null && \ check_ok "Déploiement légitime nginx:1.25 fonctionnel" || \ check_fail "Déploiement légitime échoué (vérifier les policies)" echo "" # --- 11. Test KubeArmor : blocage de l'exécution de shell --- echo "11. KubeArmor - blocage shell dans pod externe:" # Créer un pod test dans un namespace simulant l'équipe externe kubectl create namespace kubearmor-test --dry-run=client -o yaml | kubectl apply -f - > /dev/null 2>&1 CLEANUP_RESOURCES+=("namespace/kubearmor-test") kubectl run kubearmor-test-pod \ --image=busybox:1.36 \ --restart=Never \ -n kubearmor-test \ -- sleep 3600 2>/dev/null || true kubectl wait --for=condition=ready pod/kubearmor-test-pod -n kubearmor-test --timeout=60s 2>/dev/null || true # Tenter d'exécuter /bin/sh — KubeArmor doit refuser via la policy block-shell-execution SHELL_OUTPUT=$(kubectl exec -n kubearmor-test kubearmor-test-pod -- /bin/sh -c 'echo blocked-test' 2>&1 || true) if echo "$SHELL_OUTPUT" | grep -qiE "permission denied|operation not permitted|block"; then check_ok "KubeArmor bloque l'exécution de /bin/sh" else # Note: KubeArmor peut nécessiter ~30s pour propager la policy, et certains # busybox embarquent /bin/sh avec un chemin différent. Pas critique en CTF. check_fail "KubeArmor n'a pas bloqué /bin/sh (policy peut-être pas propagée, ou chemin différent)" fi echo "" # --- 12. Test RBAC : SA externe ne doit pas pouvoir lister les nodes --- echo "12. RBAC - SA external-deployer ne peut pas lister les nodes:" SA_NS="external-app" SA_NAME="external-deployer" if kubectl get sa "$SA_NAME" -n "$SA_NS" >/dev/null 2>&1; then if kubectl auth can-i list nodes \ --as="system:serviceaccount:${SA_NS}:${SA_NAME}" 2>/dev/null | grep -q "^no$"; then check_ok "SA externe ne peut pas lister les nodes" else check_fail "PROBLÈME: SA externe peut lister les nodes (énumération possible)" fi if kubectl auth can-i create clusterrolebindings \ --as="system:serviceaccount:${SA_NS}:${SA_NAME}" 2>/dev/null | grep -q "^no$"; then check_ok "SA externe ne peut pas créer de ClusterRoleBinding" else check_fail "PROBLÈME: SA externe peut créer des ClusterRoleBindings" fi if kubectl auth can-i get secrets \ --as="system:serviceaccount:${SA_NS}:${SA_NAME}" -n kube-system 2>/dev/null | grep -q "^no$"; then check_ok "SA externe ne peut pas lire les secrets de kube-system" else check_fail "PROBLÈME: SA externe peut lire les secrets de kube-system" fi else echo " ⓘ SA $SA_NAME pas encore créé (lancer 08-generate-restricted-kubeconfig.sh)" fi echo "" # --- 13. Test Kyverno : NodePort doit être refusé --- echo "13. Kyverno - rejet service NodePort:" NODEPORT_OUTPUT=$(kubectl create service nodeport kyverno-test-nodeport \ --tcp=80:80 \ --node-port=31999 \ --dry-run=server 2>&1 || true) if echo "$NODEPORT_OUTPUT" | grep -qiE "disallow-nodeport|admission webhook|denied"; then check_ok "Service NodePort correctement refusé par Kyverno" else check_fail "Service NodePort NON refusé — vérifier la ClusterPolicy disallow-nodeport-loadbalancer" fi echo "" # --- 14. Test Kyverno : ClusterRole avec wildcard refusé --- echo "14. Kyverno - rejet ClusterRole avec verbe '*':" WILDCARD_OUTPUT=$(kubectl apply --dry-run=server -f - 2>&1 <<'YAML' || true apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: kyverno-wildcard-test rules: - apiGroups: ["*"] resources: ["*"] verbs: ["*"] YAML ) if echo "$WILDCARD_OUTPUT" | grep -qiE "block-rbac-wildcards|admission webhook|denied"; then check_ok "ClusterRole avec wildcards correctement refusé par Kyverno" else check_fail "ClusterRole avec wildcards NON refusé — vérifier la ClusterPolicy block-rbac-wildcards" fi echo "" # --- Résumé --- echo "=============================================" echo " RÉSUMÉ DES VÉRIFICATIONS" echo "=============================================" echo " Succès : $PASS" echo " Échecs : $FAIL" echo "=============================================" echo "" if [[ $FAIL -eq 0 ]]; then echo "✓ Cluster sécurisé opérationnel — toutes les vérifications passées!" else echo "⚠ $FAIL vérification(s) échouée(s) — voir les détails ci-dessus." exit 1 fi