TP17 : Sécurité Kubernetes (RBAC, Pod Security, NetworkPolicy, Kyverno, Falco)

Un cluster Kubernetes installé « par défaut » est permissif : tout Pod peut joindre tous les autres, un conteneur peut tourner en root ou en mode privileged, n’importe quelle image peut être déployée. Ce TP applique la défense en profondeur à une petite application volontairement mal configurée : identité et permissions (RBAC), durcissement des Pods (securityContext, Pod Security Standards), segmentation réseau (NetworkPolicy), règles d’admission (Policy as Code avec Kyverno), puis surveillance continue (Trivy Operator, Kubescape, Falco).


Fichiers de départ : manifests.zip, policies.zip

1. Objectifs pédagogiques

À l’issue du TP, vous serez capable de :

  • appliquer le moindre privilège avec ServiceAccount, Role, RoleBinding et le vérifier avec kubectl auth can-i ;
  • durcir un Pod (runAsNonRoot, readOnlyRootFilesystem, capabilities.drop, seccompProfile) et appliquer les Pod Security Standards ;
  • écrire des NetworkPolicies default deny puis n’ouvrir que les flux nécessaires ;
  • écrire et tester des ClusterPolicies Kyverno (Audit puis Enforce) ;
  • analyser un cluster avec Trivy Operator et Kubescape, et détecter un comportement suspect avec Falco.

Les images de cette page sont des schémas explicatifs, pas des captures d’écran.

2. Environnement du TP

  • Le cluster K3s du TP14 (ou un K3s mono-nœud sur la VM de lab, ≥ 8 Go de RAM), kubectl configuré.

  • Helm installé (TP15) : il sert ici à installer Kyverno, Trivy Operator et Falco.

  • Déployer l’application de départ et constater qu’elle est acceptée sans aucune objection :

    kubectl apply -f manifests/00-app.yaml
    kubectl -n tp17 get pods

Défense en profondeur : RBAC, admission, Pod, réseau, surveillance continue Défense en profondeur : RBAC, admission, Pod, réseau, surveillance continue

3. Partie 1 : RBAC et ServiceAccount

  • Que peut faire le ServiceAccount default du namespace ?

    kubectl auth can-i --list --as=system:serviceaccount:tp17:default -n tp17
  • Compléter manifests/10-rbac.yaml (lecture seule des ConfigMaps du namespace) et l’appliquer.

  • Vérifier les permissions :

    SA=system:serviceaccount:tp17:api-sa
    kubectl auth can-i get configmaps   --as=$SA -n tp17     # yes
    kubectl auth can-i list secrets     --as=$SA -n tp17     # no
    kubectl auth can-i get configmaps   --as=$SA -n default  # no (Role = un seul namespace)
    kubectl auth can-i create pods      --as=$SA -n tp17     # no
  • Associer api-sa au Deployment api (serviceAccountName). Pourquoi automountServiceAccountToken: false ?

  • Expliquer la différence Role / ClusterRole et RoleBinding / ClusterRoleBinding. Lister les ClusterRoleBindings qui donnent cluster-admin (kubectl get clusterrolebindings -o wide | grep cluster-admin).

4. Partie 2 : securityContext

  • Durcir les deux Deployments :

    • api : image taggée, sans privileged, runAsNonRoot: true, runAsUser: 65532, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: ["ALL"] }, seccompProfile: { type: RuntimeDefault }, requests/limits ;
    • frontend : image nginxinc/nginx-unprivileged:1.29 (port 8080), mêmes options. Nginx a besoin d’écrire dans /tmp : monter un volume emptyDir.
  • Vérifier dans le conteneur :

    kubectl -n tp17 exec deploy/frontend -- id
    kubectl -n tp17 exec deploy/frontend -- touch /etc/test     # Read-only file system

5. Partie 3 : Pod Security Standards

Profil Interdit notamment
privileged Rien (aucune restriction).
baseline privileged, hostNetwork, hostPath, capacités dangereuses ajoutées.
restricted En plus : exécution en root, élévation de privilèges, capacités non retirées, seccomp absent.
  • Appliquer le profil en mode avertissement pour voir ce qui casserait :

    kubectl label ns tp17 pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/audit=restricted --overwrite
    kubectl -n tp17 rollout restart deploy
  • Une fois les Deployments conformes, passer en enforce : pod-security.kubernetes.io/enforce=restricted.

  • Tenter de créer un Pod privilégié dans le namespace : il doit être refusé par l’API server.

6. Partie 4 : NetworkPolicies

  • Avant toute policy, vérifier que l’intrus joint l’API : kubectl -n tp17 exec intruder -- wget -qO- -T 3 http://api.

  • Appliquer la policy default-deny-all de manifests/20-networkpolicies.yaml : plus rien ne passe, même le DNS.

  • Compléter : DNS sortant vers kube-dns, puis frontend → api uniquement (port 8080).

  • Vérifier : le frontend joint l’API, l’intrus non (timeout).

    kubectl -n tp17 exec deploy/frontend -- wget -qO- -T 3 http://api      # OK
    kubectl -n tp17 exec intruder        -- wget -qO- -T 3 http://api      # timeout

7. Partie 5 : Policy as Code avec Kyverno

Kyverno : la requête passe par le webhook d’admission, conforme créée, non conforme refusée Kyverno : la requête passe par le webhook d’admission, conforme créée, non conforme refusée

  • Installer Kyverno :

    helm repo add kyverno https://kyverno.github.io/kyverno/ && helm repo update
    helm install kyverno kyverno/kyverno -n kyverno --create-namespace
    kubectl -n kyverno get pods
  • Appliquer la policy modèle policies/disallow-latest-tag.yaml en mode Audit, puis consulter les rapports :

    kubectl get policyreports -A
    kubectl get policyreport -n tp17 -o yaml | grep -B2 -A6 fail
  • Écrire les policies listées dans policies/TODO-policies.md : pas de conteneur privilégié, runAsNonRoot, requests/limits obligatoires.

  • Les tester hors cluster avec la CLI Kyverno (idéal en CI) :

    docker run --rm -v "$PWD:/w" ghcr.io/kyverno/kyverno-cli:latest apply /w/policies/ --resource /w/manifests/00-app.yaml
  • Passer les policies en Enforce (validationFailureAction: Enforce, ou failureAction: Enforce par règle dans les versions récentes) et tenter d’appliquer un Deployment non conforme : noter le message de refus.

  • Comparer brièvement Kyverno et Pod Security Standards : que permet Kyverno que PSS ne permet pas (mutation, génération, vérification d’images, règles métier) ?

8. Partie 6 : Analyse continue et détection à l’exécution

  • Installer Trivy Operator et consulter les rapports de vulnérabilités et de configuration :

    helm repo add aqua https://aquasecurity.github.io/helm-charts/ && helm repo update
    helm install trivy-operator aqua/trivy-operator -n trivy-system --create-namespace
    kubectl get vulnerabilityreports -n tp17 -o wide
    kubectl get configauditreports -n tp17 -o wide
  • Lancer Kubescape sur le cluster et sur les manifests (référentiel NSA-CISA) et noter le score avant / après durcissement :

    curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
    kubescape scan framework nsa manifests/
    kubescape scan framework nsa --include-namespaces tp17
  • Installer Falco avec le pilote eBPF moderne :

    helm repo add falcosecurity https://falcosecurity.github.io/charts && helm repo update
    helm install falco falcosecurity/falco -n falco --create-namespace \
      --set driver.kind=modern_ebpf --set tty=true

    Sur K3s, le socket containerd est /run/k3s/containerd/containerd.sock. Si les alertes n’affichent pas le nom des Pods, configurez ce chemin dans les collectors du chart (voir helm show values falcosecurity/falco).

  • Déclencher des événements suspects et observer les alertes :

    kubectl -n tp17 exec -it intruder -- sh -c 'cat /etc/shadow; nc -l -p 4444 & sleep 1'
    kubectl -n falco logs -l app.kubernetes.io/name=falco -c falco | grep -E 'Notice|Warning|Critical'

Défi

  1. Écrire une policy Kyverno de mutation qui ajoute automatiquement seccompProfile: RuntimeDefault aux Pods qui n’en ont pas.
  2. Installer OPA Gatekeeper dans un namespace de test, réécrire la règle « pas de latest » en Rego (ConstraintTemplate + Constraint) et comparer la lisibilité avec Kyverno.

9. Livrables

Captures à déposer dans captures/ :

Fichier Contenu attendu
rbac-can-i.png Résultats kubectl auth can-i (yes / no)
pss-denied.png Pod privilégié refusé par le profil restricted
netpol.png Frontend → API OK, intrus → API en timeout
kyverno-enforce.png Deployment non conforme refusé avec le message de la policy
kyverno-cli.png Résultat de kyverno apply en ligne de commande
trivy-kubescape.png Rapports Trivy Operator et score Kubescape avant / après
falco-alert.png Alerte Falco après la lecture de /etc/shadow

Ainsi que les manifests durcis, les policies Kyverno et les NetworkPolicies.