Module 4d · Sécurité¶
Objectifs
- Durcir un pod : securityContext, non-root, capabilities, lecture seule.
- Cloisonner le réseau avec les NetworkPolicies (deny par défaut).
- Aller plus loin sur le RBAC (least privilege, ServiceAccounts).
- Connaître les bonnes pratiques images/secrets et savoir auditer (
popeye). - Durée : ~1 h 15 · Pré-requis : Module 4c.
Durcir le pod - securityContext¶
Par défaut, un conteneur tourne souvent en root : à éviter.
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
containers:
- name: app
image: monapp:1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"] # on retire tout, on rajoute le strict nécessaire
| Réglage | Pourquoi |
|---|---|
runAsNonRoot |
empêche l'exécution en root |
readOnlyRootFilesystem |
le conteneur ne peut pas se modifier (limite la persistance d'un attaquant) |
drop: ["ALL"] |
retire toutes les capabilities Linux superflues |
allowPrivilegeEscalation: false |
bloque l'escalade via setuid |
graph LR
D[Par défaut<br/>root · toutes capabilities<br/>rootfs inscriptible] -->|securityContext| H[Durci<br/>non-root · 0 capability<br/>rootfs en lecture seule]
Chaque réglage retire un moyen d'agir à qui entrerait dans le conteneur : c'est de la défense en profondeur - aucune couche n'est parfaite, mais empilées elles réduisent la surface au strict nécessaire.
Cloisonner le réseau - NetworkPolicy¶
Par défaut, tout pod peut parler à tout pod. Une NetworkPolicy restreint ce trafic.
Le motif sain : tout interdire, puis autoriser au cas par cas.
# Deny par défaut tout le trafic ENTRANT du namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {} # tous les pods
policyTypes: ["Ingress"]
# Puis autoriser : seuls les pods "front" peuvent joindre les pods "api" sur 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: front-vers-api
spec:
podSelector:
matchLabels: { app: api }
ingress:
- from:
- podSelector:
matchLabels: { app: front }
ports:
- port: 8080
graph LR
F[pod front] -->|autorisé : 8080| A[pod api]
O[tout autre pod] -. bloqué .-> A
default-deny mure toutes les entrées vers api ; la règle front-vers-api rouvre un
seul chemin (depuis front, sur 8080). On raisonne en liste blanche : ce qui n'est pas
explicitement autorisé est refusé - l'inverse du réseau plat par défaut.
Le CNI doit supporter les NetworkPolicies
Les NetworkPolicies ne font effet que si le plugin réseau les implémente (Calico, Cilium…). Sur certains clusters de lab/vcluster, elles peuvent être ignorées silencieusement : teste toujours l'effet réel (cf. exercice).
RBAC - least privilege¶
Rappel du module 4, version sécurité : on donne le minimum.
- Un ServiceAccount dédié par application (jamais
default). - Un Role namespacé plutôt qu'un ClusterRole quand c'est possible.
- On vérifie toujours :
kubectl auth can-i --list --as system:serviceaccount:demo:monapp
kubectl auth can-i delete pods --as system:serviceaccount:demo:monapp -n demo
Images & secrets¶
- Images : tag fixe (pas
latest), idéalement digest, base slim/distroless. - Scanner les images (Trivy, Grype) en CI avant déploiement.
- Registre privé : un secret
docker-registrypar namespace, référencé parimagePullSecretsdans le pod (ou attaché au ServiceAccount). Oublié =ImagePullBackOffunauthorized- revois le module 3. - Secrets : jamais en clair dans un manifest ; un
Secretk8s est seulement base64 (≠ chiffré) → chiffrer au repos (etcd) et/ou utiliser un gestionnaire externe (Sealed Secrets, Vault, SOPS).
Auditer le cluster - Popeye¶
Popeye (même auteur que k9s) n'est plus une vue de k9s : intégré autrefois via :popeye,
il a été retiré à la refonte v0.50. C'est de nouveau un outil séparé, lancé au terminal
(installé dans la sandbox).
C'est l'outil idéal pour mesurer l'effet des bonnes pratiques de ce module : on audite, on corrige, on ré-audite, le score monte.
Pratique¶
Le TP de ce module, dans le terminal de ta sandbox - une fois la lecture faite :
tp start 03d-rbac # créer un ServiceAccount, Role + RoleBinding, vérifier au can-i (moindre privilège)
tp start ancrage-4d # en autonomie ensuite : moindre privilège sur une autre ressource
fais la manip puis tp check (ou tp watch pour l'auto-validation) · tp hint / tp solution si tu bloques.
Pour t'entraîner librement à côté :
Exercice 4d.1 - Non-root
- Déploie un nginx standard, exec dedans,
id→ tu es root. - Ajoute un
securityContextrunAsNonRoot: true+runAsUser: le pod refuse de démarrer (image nginx veut root). Comprends pourquoi, puis utilisenginxinc/nginx-unprivileged.
Exercice 4d.2 - Deny par défaut
- Déploie deux pods
frontetapi(+ Serviceapi). Vérifie quefrontjointapi(wget). - Applique
default-deny-ingress:frontne joint plusapi. - Ajoute
front-vers-api: la communication revient, uniquement depuisfront. - (Si rien ne change après le deny : ton CNI n'applique pas les NetworkPolicies - note-le.)
Exercice 4d.3 - Audit
Au terminal : lance popeye, corrige deux findings (ex. limits manquantes, image latest), ré-audite.
Ce qu'il faut retenir¶
- Non-root + drop capabilities + rootfs en lecture seule = base du durcissement pod.
- NetworkPolicy : deny par défaut, puis autoriser finement (si le CNI le supporte).
- RBAC = least privilege, un ServiceAccount par app, on vérifie avec
auth can-i. - Un
Secretk8s n'est pas chiffré par défaut : prévoir chiffrement/gestionnaire externe. popeye(au terminal) pour mesurer et progresser.
Quiz éclair¶
Teste-toi : réponds de tête, puis déplie.
1. Cite trois réglages de base pour durcir un pod.
runAsNonRoot, capabilities.drop: ["ALL"], readOnlyRootFilesystem
(et allowPrivilegeEscalation: false).
2. Quel est le motif sain pour les NetworkPolicies ?
Deny par défaut, puis autoriser finement au cas par cas - à condition que le CNI supporte les NetworkPolicies (sinon elles sont ignorées silencieusement).
3. Un Secret Kubernetes est-il chiffré ?
Non, juste encodé en base64. La protection vient du RBAC (qui peut le lire) + chiffrement au repos (etcd) ou un gestionnaire externe (Sealed Secrets, Vault, SOPS).