Files
k8sec/partie-01-installation/07-verify-cluster.sh
Louis Labeyrie 1580a13fb6 toto
Signed-off-by: Louis Labeyrie <labeyrielouis@gmail.com>
2026-04-27 16:21:21 +02:00

293 lines
10 KiB
Bash
Executable File

#!/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