diff --git a/partie-01-installation/01-prereqs.sh b/partie-01-installation/01-prereqs.sh index dcd6775..67940f0 100755 --- a/partie-01-installation/01-prereqs.sh +++ b/partie-01-installation/01-prereqs.sh @@ -6,7 +6,6 @@ set -e # --- Détermination du rôle du nœud --- -# Priorité : option --role > argument positionnel > autodetect via hostname NODE_ROLE="" usage() { @@ -17,7 +16,6 @@ usage() { exit 1 } -# Parsing des arguments while [[ $# -gt 0 ]]; do case "$1" in --role) @@ -37,14 +35,13 @@ while [[ $# -gt 0 ]]; do esac done -# Autodetect si aucun rôle fourni if [[ -z "$NODE_ROLE" ]]; then if [[ "$(hostname)" == *"master"* ]] || [[ "$(hostname)" == *"control"* ]]; then NODE_ROLE="master" echo "Autodetect: rôle 'master' détecté via hostname ($(hostname))" else NODE_ROLE="worker" - echo "Autodetect: rôle 'worker' (hostname: $(hostname) ne contient pas 'master'/'control')" + echo "Autodetect: rôle 'worker' (hostname: $(hostname))" fi else echo "Rôle: '$NODE_ROLE' (explicite)" @@ -53,75 +50,113 @@ fi echo "=== Installation des pré-requis Kubernetes sur CentOS 10 [rôle: $NODE_ROLE] ===" # Désactiver le swap (requis par Kubernetes) -# POURQUOI: Kubernetes doit connaître la RAM réelle disponible pour le scheduling. -# Le swap introduit des latences imprévisibles et fausse les limites mémoire des pods. echo "Désactivation du swap..." sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab -# Désactiver SELinux (mode permissive pour le TD) -# POURQUOI: Simplifie le troubleshooting. En production, utiliser SELinux enforcing -# avec les policies container-selinux appropriées. -echo "Configuration de SELinux en mode permissive..." -sudo setenforce 0 || true -sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config +# Installer container-selinux AVANT containerd pour que les labels SELinux soient corrects +# POURQUOI: container-selinux fournit la policy qui confine chaque container dans le label +# container_t — un container compromis ne peut pas lire /etc/passwd ni écrire +# dans les répertoires système de l'hôte, même en root dans le container. +echo "Installation de container-selinux..." +sudo dnf install -y container-selinux + +# Maintenir SELinux en mode enforcing +# POURQUOI: Le mode permissive ne fait que logger les violations sans les bloquer — +# c'est une fausse sécurité. En enforcing + container-selinux, les appels +# système non autorisés depuis les containers sont bloqués au niveau kernel. +echo "Vérification SELinux en mode enforcing..." +sudo setenforce 1 || true +SELINUX_CURRENT=$(getenforce 2>/dev/null || echo "Unknown") +if [[ "$SELINUX_CURRENT" != "Enforcing" ]]; then + echo "ATTENTION: SELinux n'est pas en mode Enforcing (actuel: $SELINUX_CURRENT)" + echo "Vérifier /etc/selinux/config — SELINUX doit être 'enforcing'" +fi # Charger les modules kernel nécessaires -# POURQUOI: -# - overlay: Système de fichiers pour les layers des containers (utilisé par containerd) -# - br_netfilter: Permet à iptables/nftables de voir le trafic bridgé (nécessaire pour CNI) +# overlay, br_netfilter : requis par containerd et le CNI +# ip_vs, ip_vs_rr/rr/wrr/sh : requis par Cilium en mode kube-proxy replacement (IPVS) +# nf_conntrack : suivi des connexions réseau (requis par iptables/eBPF) echo "Configuration des modules kernel..." cat </dev/null || sudo modprobe nf_conntrack_ipv4 2>/dev/null || true -# Configuration sysctl pour le réseau -# POURQUOI: -# - bridge-nf-call-iptables: Le trafic bridgé passe par iptables (requis par kube-proxy) -# - ip_forward: Active le routage IP entre interfaces (requis par CNI pour pod-to-pod) +# Configuration sysctl +# bridge-nf-call-iptables/ip6tables : trafic bridgé traité par iptables (requis CNI) +# ip_forward : routage IP entre interfaces (requis pod-to-pod) +# rp_filter=0 : Cilium eBPF requiert que le reverse path filtering soit désactivé +# inotify params : requis par Cilium pour surveiller les changements de configuration echo "Configuration sysctl..." cat </dev/null || true -sudo systemctl disable firewalld 2>/dev/null || true -echo " ✓ firewalld désactivé" +# Configurer firewalld avec les règles Kubernetes +# POURQUOI: Désactiver firewalld entièrement supprime toute isolation réseau au niveau hôte. +# Si un pod bypass le CNI (via un exploit), firewalld local est la dernière barrière. +# Defense in depth : security group cloud + firewalld hôte + NetworkPolicy CNI. +echo "Configuration de firewalld avec les règles Kubernetes..." +sudo systemctl enable firewalld +sudo systemctl start firewalld + +if [[ "$NODE_ROLE" == "master" ]]; then + echo " Règles control plane..." + sudo firewall-cmd --permanent --add-port=6443/tcp # API server + sudo firewall-cmd --permanent --add-port=2379-2380/tcp # etcd client + peer + sudo firewall-cmd --permanent --add-port=10257/tcp # kube-controller-manager + sudo firewall-cmd --permanent --add-port=10259/tcp # kube-scheduler +fi + +echo " Règles communes (kubelet + réseau)..." +sudo firewall-cmd --permanent --add-port=10250/tcp # kubelet API +sudo firewall-cmd --permanent --add-port=30000-32767/tcp # NodePort services +sudo firewall-cmd --permanent --add-port=53/tcp # DNS (CoreDNS) +sudo firewall-cmd --permanent --add-port=53/udp # DNS (CoreDNS) + +echo " Règles Cilium..." +sudo firewall-cmd --permanent --add-port=4240/tcp # Cilium health checks +sudo firewall-cmd --permanent --add-port=4244/tcp # Hubble server +sudo firewall-cmd --permanent --add-port=4245/tcp # Hubble relay +sudo firewall-cmd --permanent --add-port=51871/udp # WireGuard (chiffrement inter-nœuds) +sudo firewall-cmd --permanent --add-port=8472/udp # VXLAN (fallback) + +sudo firewall-cmd --permanent --add-masquerade +sudo firewall-cmd --reload +echo " ✓ firewalld configuré" # Installation de containerd depuis le repo Docker -# POURQUOI: containerd.io du repo Docker est plus récent et mieux maintenu que celui des repos CentOS echo "Ajout du repo Docker pour containerd..." sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo echo "Installation de containerd..." sudo dnf install -y containerd.io -# Configuration de containerd sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml > /dev/null # Activer systemd cgroup driver (OBLIGATOIRE pour kubeadm) -# POURQUOI: CentOS 10 utilise systemd comme init system et gestionnaire de cgroups v2. -# Le kubelet utilise aussi systemd cgroup driver par défaut depuis K8s 1.22+. -# Si containerd et kubelet utilisent des drivers différents (cgroupfs vs systemd): -# - Conflits de gestion mémoire -# - Pods qui ne démarrent pas ou crashent -# - Métriques incorrectes -# - Comportement OOM imprévisible echo "Activation du driver cgroup systemd pour containerd..." sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml @@ -144,31 +179,48 @@ if [[ "$NODE_ROLE" == "master" ]]; then echo "Installation de kubeadm, kubelet, kubectl (control plane)..." sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes else - echo "Installation de kubeadm, kubelet (worker — kubectl non requis)..." + echo "Installation de kubeadm, kubelet (worker)..." sudo dnf install -y kubelet kubeadm --disableexcludes=kubernetes fi -# Verrouiller les versions pour éviter les mises à jour accidentelles -# POURQUOI: Une mise à jour non planifiée de kubelet peut casser le cluster. -# L'upgrade doit être fait de manière contrôlée via kubeadm upgrade. echo "Verrouillage des versions..." sudo dnf install -y 'dnf-command(versionlock)' 2>/dev/null || true if [[ "$NODE_ROLE" == "master" ]]; then - sudo dnf versionlock add kubelet kubeadm kubectl 2>/dev/null || echo "Note: versionlock non disponible, pensez à surveiller les mises à jour" + sudo dnf versionlock add kubelet kubeadm kubectl 2>/dev/null || echo "Note: versionlock non disponible" else - sudo dnf versionlock add kubelet kubeadm 2>/dev/null || echo "Note: versionlock non disponible, pensez à surveiller les mises à jour" + sudo dnf versionlock add kubelet kubeadm 2>/dev/null || echo "Note: versionlock non disponible" +fi + +# Installer Helm sur le master (requis pour Cilium, KubeArmor, Kyverno) +if [[ "$NODE_ROLE" == "master" ]]; then + if ! command -v helm &>/dev/null; then + echo "Installation de Helm..." + HELM_VERSION="v3.17.3" + curl -L "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" -o /tmp/helm.tar.gz + tar -xzf /tmp/helm.tar.gz -C /tmp + sudo mv /tmp/linux-amd64/helm /usr/local/bin/helm + rm -rf /tmp/linux-amd64 /tmp/helm.tar.gz + echo " ✓ Helm ${HELM_VERSION} installé" + else + echo " ✓ Helm déjà installé: $(helm version --short)" + fi fi -# Activer kubelet sudo systemctl enable kubelet echo "" echo "=== Vérifications ===" echo "Swap désactivé: $(free -h | grep Swap | awk '{print $2}') (doit être 0)" -echo "SELinux: $(getenforce)" -echo "Modules kernel: $(lsmod | grep -E 'overlay|br_netfilter' | wc -l)/2 chargés" +SELINUX_STATUS=$(getenforce 2>/dev/null || echo "Unknown") +if [[ "$SELINUX_STATUS" == "Enforcing" ]]; then + echo "SELinux: $SELINUX_STATUS ✓" +else + echo "SELinux: $SELINUX_STATUS ⚠ ATTENTION: doit être Enforcing" +fi +echo "Modules kernel: $(lsmod | grep -E 'overlay|br_netfilter|ip_vs|nf_conntrack' | wc -l)/7 chargés" +echo "firewalld: $(systemctl is-active firewalld)" echo "containerd: $(systemctl is-active containerd)" -echo "SystemdCgroup: $(grep 'SystemdCgroup = true' /etc/containerd/config.toml > /dev/null && echo 'activé' || echo 'ATTENTION: non activé!')" +echo "SystemdCgroup: $(grep 'SystemdCgroup = true' /etc/containerd/config.toml > /dev/null && echo 'activé ✓' || echo 'ATTENTION: non activé!')" echo "" echo "✓ Pré-requis installés avec succès!" echo "Version kubeadm: $(kubeadm version -o short)" @@ -176,3 +228,6 @@ echo "Version kubelet: $(kubelet --version)" if command -v kubectl &>/dev/null; then echo "Version kubectl: $(kubectl version --client -o yaml | grep gitVersion)" fi +if command -v helm &>/dev/null; then + echo "Version helm: $(helm version --short)" +fi diff --git a/partie-01-installation/02-init-control-plane.sh b/partie-01-installation/02-init-control-plane.sh index eb8fa4d..f938247 100755 --- a/partie-01-installation/02-init-control-plane.sh +++ b/partie-01-installation/02-init-control-plane.sh @@ -4,26 +4,258 @@ set -e -echo "=== Initialisation du Control Plane Kubernetes ===" +echo "=== Initialisation du Control Plane Kubernetes (hardened) ===" -# Définir le réseau pod (utilisé par Flannel) -POD_NETWORK_CIDR="10.244.0.0/16" +APISERVER_IP=$(hostname -I | awk '{print $1}') +POD_CIDR="10.244.0.0/16" +SERVICE_CIDR="10.96.0.0/12" -echo "Initialisation de kubeadm avec le réseau pod $POD_NETWORK_CIDR..." -sudo kubeadm init --pod-network-cidr=$POD_NETWORK_CIDR --apiserver-advertise-address=$(hostname -I | awk '{print $1}') +echo "IP API server : $APISERVER_IP" +echo "Pod CIDR : $POD_CIDR" +echo "Service CIDR : $SERVICE_CIDR" +echo "" +# --- Audit logging --- +echo "Création de la politique d'audit..." +sudo mkdir -p /var/log/kubernetes/audit +sudo mkdir -p /etc/kubernetes/audit + +sudo tee /etc/kubernetes/audit/audit-policy.yaml > /dev/null <<'EOF' +apiVersion: audit.k8s.io/v1 +kind: Policy +omitStages: + - RequestReceived +rules: + # Tracer tous les accès aux secrets et configmaps (données sensibles) + - level: RequestResponse + resources: + - group: "" + resources: ["secrets", "configmaps"] + + # Tracer les modifications RBAC (vecteur d'escalade de privilèges) + - level: RequestResponse + resources: + - group: "rbac.authorization.k8s.io" + resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"] + + # Tracer exec/portforward/attach (accès interactif aux pods — vecteur d'attaque courant) + - level: RequestResponse + resources: + - group: "" + resources: ["pods/exec", "pods/portforward", "pods/attach"] + + # Tracer toutes les créations/suppressions/modifications (niveau Metadata pour réduire le volume) + - level: Metadata + verbs: ["create", "delete", "patch", "update"] + + # Ignorer le bruit des health checks et composants systèmes + - level: None + users: ["system:kube-proxy"] + verbs: ["watch"] + resources: + - group: "" + resources: ["endpoints", "services", "services/status"] + - level: None + users: ["system:apiserver"] + verbs: ["get"] + resources: + - group: "" + resources: ["namespaces"] + - level: None + nonResourceURLs: ["/healthz*", "/readyz*", "/livez*", "/metrics"] + + # Défaut : niveau Metadata pour tout le reste + - level: Metadata +EOF + +# --- Chiffrement etcd at-rest --- +echo "Génération de la clé de chiffrement etcd..." +sudo mkdir -p /etc/kubernetes/encryption + +ENCRYPTION_KEY=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) + +sudo tee /etc/kubernetes/encryption/encryption-config.yaml > /dev/null < /dev/null <<'EOF' +apiVersion: apiserver.config.k8s.io/v1 +kind: AdmissionConfiguration +plugins: + - name: EventRateLimit + configuration: + apiVersion: eventratelimit.admission.k8s.io/v1alpha1 + kind: Configuration + limits: + - type: Namespace + qps: 50 + burst: 100 + cacheSize: 2000 + - type: User + qps: 10 + burst: 50 + - name: PodSecurity + configuration: + apiVersion: pod-security.admission.config.k8s.io/v1 + kind: PodSecurityConfiguration + defaults: + enforce: "baseline" + enforce-version: "latest" + audit: "restricted" + audit-version: "latest" + warn: "restricted" + warn-version: "latest" + exemptions: + namespaces: + - kube-system + - kubearmor + - kyverno + - cilium-system + usernames: [] + runtimeClasses: [] +EOF + +# --- kubeadm config --- +echo "Création de la configuration kubeadm..." + +cat > /tmp/kubeadm-config.yaml <): " CA_HASH +echo "" + +# Validation des formats avant toute exécution +# POURQUOI: Passer une commande non validée à sudo permet l'exécution de commandes +# arbitraires en root. On reconstruit l'appel kubeadm avec des arguments +# explicites pour éviter toute injection. + +if [[ ! "$MASTER_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$|^[a-zA-Z0-9._-]+$ ]]; then + echo "Erreur: format IP/hostname master invalide" + exit 1 +fi + +if [[ ! "$TOKEN" =~ ^[a-z0-9]{6}\.[a-z0-9]{16}$ ]]; then + echo "Erreur: format token invalide (attendu: xxxxxx.yyyyyyyyyyyyyyyy)" + exit 1 +fi + +if [[ ! "$CA_HASH" =~ ^sha256:[a-f0-9]{64}$ ]]; then + echo "Erreur: format CA hash invalide (attendu: sha256:<64 caractères hex>)" + exit 1 +fi echo "" -echo "Exécution de: $JOIN_COMMAND" -sudo $JOIN_COMMAND +echo "Composants validés:" +echo " Master : ${MASTER_IP}:6443" +echo " Token : ${TOKEN:0:6}.****************" +echo " CA Hash : sha256:${CA_HASH:7:8}..." +echo "" + +sudo kubeadm join "${MASTER_IP}:6443" \ + --token "$TOKEN" \ + --discovery-token-ca-cert-hash "$CA_HASH" echo "" echo "✓ Worker joint au cluster avec succès!" diff --git a/partie-01-installation/04-install-cilium.sh b/partie-01-installation/04-install-cilium.sh new file mode 100755 index 0000000..c877297 --- /dev/null +++ b/partie-01-installation/04-install-cilium.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# Partie 1 - Installation du CNI Cilium + Hubble + Tetragon +# À exécuter sur le nœud MASTER +# Remplace Flannel : Cilium apporte NetworkPolicy L7, chiffrement WireGuard, +# observabilité Hubble et runtime security Tetragon. + +set -e + +CILIUM_VERSION="1.17.3" +CILIUM_CLI_VERSION="v0.18.4" +TETRAGON_VERSION="1.4.0" + +APISERVER_IP=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' | grep -oP '(?<=https://)[^:]+') + +echo "=== Installation Cilium ${CILIUM_VERSION} + Hubble + Tetragon ${TETRAGON_VERSION} ===" +echo "API server : ${APISERVER_IP}:6443" +echo "" + +# --- Cilium CLI --- +echo "Installation du CLI Cilium ${CILIUM_CLI_VERSION}..." +CILIUM_CLI_ARCH="amd64" +CILIUM_CLI_TAR="cilium-linux-${CILIUM_CLI_ARCH}.tar.gz" +CILIUM_CLI_URL="https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/${CILIUM_CLI_TAR}" +CILIUM_CLI_SHA_URL="${CILIUM_CLI_URL}.sha256sum" + +curl -L --fail "$CILIUM_CLI_URL" -o "/tmp/${CILIUM_CLI_TAR}" +curl -L --fail "$CILIUM_CLI_SHA_URL" -o "/tmp/${CILIUM_CLI_TAR}.sha256sum" + +cd /tmp +sha256sum --check "${CILIUM_CLI_TAR}.sha256sum" || { + echo "ERREUR: checksum cilium-cli invalide. Abandon." + exit 1 +} +cd - + +tar -xzf "/tmp/${CILIUM_CLI_TAR}" -C /tmp cilium +sudo mv /tmp/cilium /usr/local/bin/cilium +sudo chmod +x /usr/local/bin/cilium +rm -f "/tmp/${CILIUM_CLI_TAR}" "/tmp/${CILIUM_CLI_TAR}.sha256sum" +echo " ✓ cilium CLI installé" + +# --- Helm repos --- +echo "Ajout des repos Helm..." +helm repo add cilium https://helm.cilium.io/ 2>/dev/null || helm repo update cilium +helm repo update + +# --- Cilium --- +echo "" +echo "Installation de Cilium ${CILIUM_VERSION}..." +echo " Options : kubeProxyReplacement + WireGuard + Hubble + policyEnforcementMode=default" +echo "" + +helm upgrade --install cilium cilium/cilium \ + --version "${CILIUM_VERSION}" \ + --namespace kube-system \ + --set kubeProxyReplacement=true \ + --set k8sServiceHost="${APISERVER_IP}" \ + --set k8sServicePort=6443 \ + --set encryption.enabled=true \ + --set encryption.type=wireguard \ + --set hubble.enabled=true \ + --set hubble.relay.enabled=true \ + --set hubble.ui.enabled=true \ + --set policyEnforcementMode=default \ + --set nodeinit.enabled=true \ + --set ipam.mode=kubernetes \ + --wait --timeout=10m + +echo "" +echo "Attente que Cilium soit opérationnel..." +cilium status --wait --wait-duration=5m + +echo "" +echo "✓ Cilium opérationnel" + +# --- Tetragon --- +echo "" +echo "Installation de Tetragon ${TETRAGON_VERSION}..." +echo " Tetragon = observabilité runtime eBPF profonde (syscalls, fichiers, réseau)" + +helm upgrade --install tetragon cilium/tetragon \ + --version "${TETRAGON_VERSION}" \ + --namespace kube-system \ + --set tetragon.exportFilename="/var/log/tetragon/tetragon.log" \ + --wait --timeout=5m + +echo "" +echo "Application des TracingPolicies de base..." + +# Surveiller toutes les exécutions de processus (détecte les shells lancés dans des containers, +# les outils de reconnaissance, les tentatives d'escalade) +kubectl apply -f - <<'EOF' +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: monitor-process-exec +spec: + kprobes: + - call: "sys_execve" + syscall: true + args: + - index: 0 + type: "string" + - index: 1 + type: "string_array" +EOF + +# Surveiller les accès aux fichiers sensibles du cluster et de l'hôte +kubectl apply -f - <<'EOF' +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: monitor-sensitive-file-access +spec: + kprobes: + - call: "sys_openat" + syscall: true + args: + - index: 1 + type: "string" + selectors: + - matchArgs: + - index: 1 + operator: "Prefix" + values: + - "/etc/kubernetes" + - "/var/lib/etcd" + - "/run/secrets/kubernetes.io" + - "/proc/1/" +EOF + +echo " ✓ TracingPolicies appliquées" + +# --- NetworkPolicy deny-all par défaut --- +echo "" +echo "Application de la NetworkPolicy deny-all dans le namespace default..." + +kubectl apply -f - <<'EOF' +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +EOF + +echo " ✓ NetworkPolicy deny-all appliquée (namespace: default)" + +# --- Vérifications --- +echo "" +echo "=== Vérifications ===" +echo "" +echo "1. Statut Cilium:" +cilium status +echo "" +echo "2. Pods Tetragon:" +kubectl get pods -n kube-system -l app.kubernetes.io/name=tetragon +echo "" +echo "3. TracingPolicies:" +kubectl get tracingpolicies 2>/dev/null || echo " (CRD TracingPolicy en cours d'initialisation)" +echo "" +echo "4. Nœuds (doivent être Ready):" +kubectl get nodes +echo "" +echo "✓ Cilium + Hubble + Tetragon installés avec succès!" +echo "" +echo "Accès à Hubble UI (depuis le master) :" +echo " kubectl port-forward -n kube-system svc/hubble-ui 8080:80 &" +echo " Puis ouvrir http://localhost:8080" +echo "" +echo "Logs Tetragon en temps réel :" +echo " kubectl logs -n kube-system -l app.kubernetes.io/name=tetragon -f | jq '.'" diff --git a/partie-01-installation/04-install-flannel.sh b/partie-01-installation/04-install-flannel.sh deleted file mode 100755 index 8d67409..0000000 --- a/partie-01-installation/04-install-flannel.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# Partie 1 - Installation du CNI Flannel -# À exécuter sur le nœud MASTER - -set -e - -echo "=== Installation du CNI Flannel ===" - -# Télécharger et appliquer le manifest Flannel -echo "Application du manifest Flannel..." -kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml - -echo "" -echo "Attente du déploiement de Flannel..." -kubectl wait --for=condition=ready pod -l app=flannel -n kube-flannel --timeout=180s - -echo "" -echo "✓ Flannel installé avec succès!" -echo "" -echo "Vérification des pods réseau:" -kubectl get pods -n kube-flannel -echo "" -echo "Vérification des nœuds (tous doivent être Ready):" -kubectl get nodes diff --git a/partie-01-installation/05-install-kubearmor.sh b/partie-01-installation/05-install-kubearmor.sh new file mode 100755 index 0000000..e591b03 --- /dev/null +++ b/partie-01-installation/05-install-kubearmor.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# Partie 1 - Installation de KubeArmor +# À exécuter sur le nœud MASTER +# +# KubeArmor = policies runtime DÉCLARATIVES (allow/block fichiers, processus, réseau) +# Complément à Tetragon : KubeArmor BLOQUE, Tetragon TRACE en profondeur. +# Les deux sont indépendants et complémentaires : +# - Attaque bloquée → KubeArmor (action immédiate) +# - Analyse forensique → Tetragon (logs syscall détaillés) + +set -e + +KUBEARMOR_VERSION="1.4.3" + +echo "=== Installation KubeArmor ${KUBEARMOR_VERSION} ===" +echo "" + +# --- Installation via Helm --- +echo "Ajout du repo Helm KubeArmor..." +helm repo add kubearmor https://kubearmor.github.io/charts 2>/dev/null || true +helm repo update + +echo "" +echo "Installation de KubeArmor ${KUBEARMOR_VERSION}..." +helm upgrade --install kubearmor kubearmor/kubearmor \ + --namespace kubearmor \ + --create-namespace \ + --version "${KUBEARMOR_VERSION}" \ + --set kubearmor.defaultFilePosture=block \ + --set kubearmor.defaultCapabilitiesPosture=audit \ + --set kubearmor.defaultNetworkPosture=audit \ + --wait --timeout=5m + +echo "" +echo "Attente que KubeArmor soit opérationnel..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=kubearmor \ + -n kubearmor \ + --timeout=120s + +echo " ✓ KubeArmor opérationnel" + +# --- ClusterKubeArmorPolicies de base --- +# Ces policies s'appliquent à TOUS les pods du cluster. +# Le mode Audit log les violations sans bloquer — basculer en Block après validation. +# Le mode Block bloque la violation et génère une alerte. + +echo "" +echo "Application des ClusterKubeArmorPolicies de base..." + +# Policy 1 : Bloquer l'exécution de shells dans les containers +# POURQUOI: Un attaquant qui obtient l'exécution de code dans un container essaiera +# immédiatement d'ouvrir un shell pour explorer l'environnement. +# Bloquer /bin/sh, /bin/bash, etc. coupe ce vecteur. +kubectl apply -f - <<'EOF' +apiVersion: security.kubearmor.com/v1 +kind: ClusterKubeArmorPolicy +metadata: + name: block-shell-execution + annotations: + description: "Bloque l'exécution de shells dans tous les containers" +spec: + selector: + matchLabels: {} + process: + matchPaths: + - path: /bin/sh + - path: /bin/bash + - path: /usr/bin/sh + - path: /usr/bin/bash + - path: /bin/dash + - path: /bin/zsh + - path: /usr/bin/zsh + action: Block +EOF + +# Policy 2 : Bloquer l'accès aux paths sensibles de l'hôte depuis les containers +# POURQUOI: /proc/1/ expose les variables d'environnement du processus init (souvent des tokens). +# /etc/kubernetes/ contient les certificats et kubeconfigs du cluster. +# /var/lib/etcd/ contient les données etcd (même si chiffrées, les lire est suspect). +kubectl apply -f - <<'EOF' +apiVersion: security.kubearmor.com/v1 +kind: ClusterKubeArmorPolicy +metadata: + name: block-sensitive-host-paths + annotations: + description: "Bloque l'accès aux paths sensibles de l'hôte depuis les containers" +spec: + selector: + matchLabels: {} + file: + matchDirectories: + - dir: /proc/1/ + recursive: false + - dir: /etc/kubernetes/ + recursive: true + - dir: /var/lib/etcd/ + recursive: true + - dir: /run/secrets/kubernetes.io/ + recursive: true + action: Block +EOF + +# Policy 3 : Audit des outils de reconnaissance réseau +# POURQUOI: curl, wget, nc sont légitimes dans certaines apps mais sont aussi les premiers +# outils utilisés pour l'exfiltration de données ou la communication C2. +# On commence en Audit pour identifier les usages légitimes avant de bloquer. +kubectl apply -f - <<'EOF' +apiVersion: security.kubearmor.com/v1 +kind: ClusterKubeArmorPolicy +metadata: + name: audit-network-recon-tools + annotations: + description: "Audit (puis potentiellement Block) des outils réseau dans les containers" +spec: + selector: + matchLabels: {} + process: + matchPaths: + - path: /usr/bin/curl + - path: /usr/bin/wget + - path: /bin/nc + - path: /usr/bin/nc + - path: /usr/bin/ncat + - path: /usr/bin/nmap + - path: /usr/bin/tcpdump + action: Audit +EOF + +# Policy 4 : Bloquer les outils de manipulation de capabilities et namespaces +# POURQUOI: nsenter, unshare, capsh sont utilisés pour l'évasion de containers. +# Un attaquant dans un container peut tenter de rejoindre le namespace hôte. +kubectl apply -f - <<'EOF' +apiVersion: security.kubearmor.com/v1 +kind: ClusterKubeArmorPolicy +metadata: + name: block-namespace-escape-tools + annotations: + description: "Bloque les outils d'évasion de containers (nsenter, unshare, capsh)" +spec: + selector: + matchLabels: {} + process: + matchPaths: + - path: /usr/bin/nsenter + - path: /usr/sbin/nsenter + - path: /usr/bin/unshare + - path: /usr/bin/capsh + action: Block +EOF + +echo " ✓ 4 ClusterKubeArmorPolicies appliquées" + +# --- Vérifications --- +echo "" +echo "=== Vérifications ===" +echo "" +echo "1. Pods KubeArmor:" +kubectl get pods -n kubearmor +echo "" +echo "2. ClusterKubeArmorPolicies actives:" +kubectl get clusterubearmorpolicies 2>/dev/null || kubectl get clusterkubearmorpholicies 2>/dev/null || \ + kubectl get -f - <<< "$(kubectl api-resources --api-group=security.kubearmor.com -o name 2>/dev/null | head -1)" 2>/dev/null || \ + kubectl get kubearmorpholicies --all-namespaces 2>/dev/null || \ + echo " (CRDs en cours d'initialisation — vérifier dans 30s)" +echo "" +echo "✓ KubeArmor installé avec succès!" +echo "" +echo "Logs KubeArmor en temps réel :" +echo " kubectl logs -n kubearmor -l app=kubearmor -f" +echo "" +echo "NOTE: La policy 'audit-network-recon-tools' est en mode Audit." +echo " Après validation des apps légitimes, passer en Block :" +echo " kubectl patch clusterkubearmorpholicy audit-network-recon-tools --type=merge -p '{\"spec\":{\"action\":\"Block\"}}'" diff --git a/partie-01-installation/05-verify-cluster.sh b/partie-01-installation/05-verify-cluster.sh deleted file mode 100755 index acf4557..0000000 --- a/partie-01-installation/05-verify-cluster.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# Partie 1 - Vérification du cluster -# À exécuter sur le nœud MASTER - -set -e - -echo "=== Vérification du cluster Kubernetes ===" -echo "" - -echo "1. Vérification des nœuds:" -kubectl get nodes -o wide -echo "" - -echo "2. Vérification des composants du control plane:" -kubectl get pods -n kube-system -echo "" - -echo "3. Vérification du CNI Flannel:" -kubectl get pods -n kube-flannel -echo "" - -echo "4. Test de déploiement d'un pod simple:" -kubectl run test-pod --image=nginx --restart=Never --rm -i --tty -- echo "✓ Connectivité pod fonctionnelle" -echo "" - -echo "5. Vérification de la communication inter-pods:" -kubectl create deployment nginx-test --image=nginx --replicas=3 -kubectl wait --for=condition=available deployment/nginx-test --timeout=60s -echo "✓ Déploiement réussi" -kubectl get pods -o wide -l app=nginx-test -echo "" - -echo "Nettoyage du test..." -kubectl delete deployment nginx-test - -echo "" -echo "=== Cluster vérifié avec succès! ===" diff --git a/partie-01-installation/06-install-kyverno.sh b/partie-01-installation/06-install-kyverno.sh new file mode 100755 index 0000000..0f55e5c --- /dev/null +++ b/partie-01-installation/06-install-kyverno.sh @@ -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//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" diff --git a/partie-01-installation/07-verify-cluster.sh b/partie-01-installation/07-verify-cluster.sh new file mode 100755 index 0000000..9a4e805 --- /dev/null +++ b/partie-01-installation/07-verify-cluster.sh @@ -0,0 +1,206 @@ +#!/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 clusterkubearmorpholicies 2>/dev/null | grep -c "block\|audit" || echo "0") +if [[ "$KUBEARMOR_POLICIES" -ge 4 ]]; then + check_ok "ClusterKubeArmorPolicies actives: ${KUBEARMOR_POLICIES}" +else + check_fail "ClusterKubeArmorPolicies insuffisantes (trouvées: ${KUBEARMOR_POLICIES}, attendues: 4)" +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 2>/dev/null | grep -c "pass\|fail" || \ + kubectl get clusterpolicies 2>/dev/null | tail -n +2 | wc -l || echo "0") +if [[ "$KYVERNO_POLICIES" -ge 8 ]]; then + check_ok "ClusterPolicies Kyverno actives: ${KYVERNO_POLICIES}" +else + check_fail "ClusterPolicies Kyverno insuffisantes (trouvées: ${KYVERNO_POLICIES}, attendues: 8)" +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 "" + +# --- 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 diff --git a/partie-01-installation/08-generate-restricted-kubeconfig.sh b/partie-01-installation/08-generate-restricted-kubeconfig.sh new file mode 100755 index 0000000..d76182f --- /dev/null +++ b/partie-01-installation/08-generate-restricted-kubeconfig.sh @@ -0,0 +1,225 @@ +#!/bin/bash +# Partie 1 - Génération d'un kubeconfig restreint pour l'équipe externe +# À exécuter sur le nœud MASTER +# +# Ce kubeconfig permet à l'équipe externe de : +# ✓ Déployer une application dans le namespace "external-app" +# ✓ Gérer Deployments, Services, Pods, ConfigMaps dans ce namespace +# ✗ Accéder aux autres namespaces +# ✗ Créer des ClusterRoles ou ClusterRoleBindings +# ✗ Exécuter kubectl exec (pods/exec interdit) +# ✗ Lire les secrets existants (create/update seulement) + +set -e + +NAMESPACE="external-app" +SA_NAME="external-deployer" +TOKEN_DURATION="24h" +OUTPUT_FILE="./external-team-kubeconfig.yaml" + +echo "=== Génération du kubeconfig restreint pour l'équipe externe ===" +echo "" +echo "Namespace : $NAMESPACE" +echo "Compte : $SA_NAME" +echo "Expiration : $TOKEN_DURATION" +echo "Fichier : $OUTPUT_FILE" +echo "" + +# --- Namespace --- +echo "1. Création du namespace ${NAMESPACE}..." +kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - + +# Labeliser le namespace pour PodSecurity (enforce: restricted pour l'équipe externe) +kubectl label namespace "$NAMESPACE" \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/enforce-version=latest \ + pod-security.kubernetes.io/audit=restricted \ + pod-security.kubernetes.io/warn=restricted \ + --overwrite + +echo " ✓ Namespace créé avec PodSecurity=restricted" + +# --- ServiceAccount --- +echo "" +echo "2. Création du ServiceAccount ${SA_NAME}..." +kubectl create serviceaccount "$SA_NAME" \ + --namespace "$NAMESPACE" \ + --dry-run=client -o yaml | kubectl apply -f - +echo " ✓ ServiceAccount créé" + +# --- Role namespace-scoped (pas ClusterRole) --- +echo "" +echo "3. Création du Role (namespace-scoped)..." + +kubectl apply -f - < "$OUTPUT_FILE" <