Chapitre 7 : Sécurité¶
!!! abstract "Objectifs du chapitre" - Décrire le parcours d'une requête dans l'API : authentification, autorisation, admission - Distinguer les utilisateurs et les ServiceAccounts, et appliquer le moindre privilège avec RBAC - Durcir un Pod avec un SecurityContext - Expliquer les niveaux des Pod Security Standards et les appliquer à un namespace - Contrôler les flux réseau entre Pods avec des NetworkPolicies - Acquis d'apprentissage visé : AA4
1. Le modèle de sécurité¶
Par défaut, un cluster est ouvert : n'importe quel Pod peut joindre n'importe quel autre, et un conteneur mal configuré dispose de beaucoup de droits. La sécurité se construit en couches, à chacune desquelles on applique le moindre privilège.
flowchart LR
R["Requête<br/>(kubectl, Pod)"] --> A1["Authentification<br/>qui es-tu ?"]
A1 --> A2["Autorisation (RBAC)<br/>as-tu le droit ?"]
A2 --> A3["Admission<br/>la demande est-elle conforme ?"]
A3 --> E[("Datastore<br/>état du cluster")]
| Couche | Question | Outils de ce chapitre |
|---|---|---|
| Accès à l'API | Qui peut faire quoi sur le cluster ? | ServiceAccounts, RBAC |
| Pods | Que peut faire un conteneur sur son nœud ? | SecurityContext, Pod Security Standards |
| Réseau | Qui peut parler à qui ? | NetworkPolicies |
Les secrets (chapitre 4) complètent l'ensemble : leur lecture est elle-même gouvernée par RBAC.
2. Identités : utilisateurs et ServiceAccounts¶
Deux types d'identités appellent l'API :
| Identité | Représente | Exemple |
|---|---|---|
| Utilisateur | Une personne, via un certificat ou un fichier kubeconfig | Vous, avec kubectl |
| ServiceAccount | Un programme qui s'exécute dans un Pod | Un opérateur, un job de déploiement |
Kubernetes ne gère pas les comptes d'utilisateurs comme des objets. Les ServiceAccounts, eux, sont des objets d'un namespace.
- Chaque namespace possède un ServiceAccount
default, attribué aux Pods qui n'en précisent pas. - Par défaut, le jeton du ServiceAccount est monté dans chaque Pod (dans
/var/run/secrets/kubernetes.io/serviceaccount), même si l'application n'appelle jamais l'API. - Un attaquant qui prend la main sur un conteneur peut utiliser ce jeton : on le désactive quand il est inutile.
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-sa
automountServiceAccountToken: false # pas de jeton monté dans les Pods qui l'utilisent
---
apiVersion: v1
kind: Pod
metadata:
name: api
spec:
serviceAccountName: api-sa
containers:
- name: api
image: monimage:1.0.0
Votre compte kubectl
Avec k3s, votre fichier kubeconfig donne les droits d'administrateur du cluster. Pour tester les droits d'un autre compte, on n'utilise donc pas son identité, mais l'option --as de kubectl (section 3).
3. RBAC : qui a le droit de faire quoi¶
RBAC (Role-Based Access Control) autorise ou non une action selon des règles. Sans règle, l'action est refusée : RBAC ne sait qu'ajouter des permissions, jamais en retirer.
| Objet | Portée | Rôle |
|---|---|---|
| Role | Un namespace | Liste de permissions |
| ClusterRole | Tout le cluster | Liste de permissions (ou règles pour des ressources hors namespace) |
| RoleBinding | Un namespace | Attribue un Role (ou ClusterRole) à des sujets, dans ce namespace |
| ClusterRoleBinding | Tout le cluster | Attribue un ClusterRole à des sujets, sur tout le cluster |
flowchart LR
S["Sujet<br/>ServiceAccount dev-viewer"] -- "RoleBinding" --> R["Role<br/>pod-reader"]
R -- "règles" --> P["Verbes get, list, watch<br/>sur pods, pods/log, services"]
Une règle indique des ressources, des verbes et leur groupe d'API :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
rules:
- apiGroups: [""] # "" = groupe de base (core)
resources: ["pods", "pods/log", "services"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-viewer-binding
subjects:
- kind: ServiceAccount
name: dev-viewer
namespace: tp-k8s
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
| Verbe | Action |
|---|---|
get, list, watch |
Lire un objet, lister, suivre les changements |
create, update, patch, delete |
Créer, modifier, supprimer |
Attention aux Secrets
get et list sur les Secrets donnent accès à leur contenu. Ne les accordez qu'aux comptes qui en ont réellement besoin.
On vérifie les droits avec kubectl auth can-i, en se faisant passer pour un autre compte avec --as :
kubectl auth can-i list pods --as=system:serviceaccount:tp-k8s:dev-viewer -n tp-k8s
kubectl auth can-i delete pods --as=system:serviceaccount:tp-k8s:dev-viewer -n tp-k8s
kubectl auth can-i --list --as=system:serviceaccount:tp-k8s:dev-viewer -n tp-k8s
4. SecurityContext : durcir un conteneur¶
Par défaut, un conteneur peut s'exécuter en root et conserve de nombreux droits. Le SecurityContext les restreint, au niveau du Pod ou d'un conteneur.
| Champ | Effet |
|---|---|
runAsNonRoot: true |
Refuse de démarrer un conteneur qui s'exécuterait en root |
runAsUser |
Impose un identifiant d'utilisateur |
allowPrivilegeEscalation: false |
Interdit à un processus d'obtenir plus de droits que son parent |
readOnlyRootFilesystem: true |
Système de fichiers du conteneur en lecture seule |
capabilities.drop: ["ALL"] |
Retire toutes les « capabilities » Linux (fragments des droits de root) |
privileged: true |
Donne presque tous les droits de l'hôte : à proscrire |
seccompProfile: RuntimeDefault |
Filtre les appels système avec le profil par défaut du runtime |
spec:
securityContext: # niveau Pod
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: nginxinc/nginx-unprivileged:1.27-alpine
securityContext: # niveau conteneur
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {} # dossier inscriptible malgré le système en lecture seule
Points à connaître :
- l'image doit être compatible : une image qui exige root ne démarre pas avec
runAsNonRoot;nginx-unprivilegedécoute sur le port 8080 (un port inférieur à 1024 demanderait un droit supplémentaire) ; - avec
readOnlyRootFilesystem, les dossiers où l'application écrit (/tmp, caches) doivent être desemptyDir; - certaines applications, comme les bases de données, imposent des exceptions : on les documente.
5. Pod Security Standards¶
Plutôt que de vérifier chaque Pod à la main, on applique une politique à un namespace, avec des labels. C'est l'admission de sécurité des Pods (Pod Security Admission), intégrée à Kubernetes. Trois niveaux, du plus permissif au plus strict :
| Niveau | Principe |
|---|---|
| privileged | Aucune restriction |
| baseline | Interdit les configurations dangereuses connues (Pod privilégié, accès au réseau ou aux processus de l'hôte, hostPath...) ; peu de contraintes sur les applications |
| restricted | Applique les bonnes pratiques de durcissement : utilisateur non-root, pas d'élévation de privilèges, capabilities supprimées, profil seccomp défini |
Trois modes d'application, choisis par label :
| Label | Effet |
|---|---|
pod-security.kubernetes.io/enforce |
Le Pod non conforme est refusé |
pod-security.kubernetes.io/warn |
Un avertissement est affiché, le Pod est accepté |
pod-security.kubernetes.io/audit |
Une trace est écrite dans le journal d'audit |
kubectl label namespace tp-k8s pod-security.kubernetes.io/enforce=baseline
kubectl label namespace tp-k8s pod-security.kubernetes.io/warn=restricted --overwrite
Le contrôle s'applique à la création des Pods : ceux qui existent déjà ne sont pas arrêtés. Une bonne démarche consiste à activer warn au niveau visé pour mesurer l'écart, puis enforce une fois les manifestes corrigés.
6. NetworkPolicies¶
Par défaut, tous les Pods d'un cluster peuvent communiquer entre eux. Une NetworkPolicy est un pare-feu au niveau des Pods : elle décrit les flux autorisés.
Principes :
- une politique sélectionne des Pods (
podSelector) et s'applique au trafic entrant (Ingress), sortant (Egress) ou aux deux ; - dès qu'un Pod est sélectionné par une politique pour un sens, tout ce qui n'est pas explicitement autorisé est refusé dans ce sens ;
- les politiques s'additionnent : le trafic est autorisé si au moins une politique l'autorise ;
- elles ne filtrent que les couches 3 et 4 (adresses, ports), pas le contenu HTTP.
Pour qu'elles soient appliquées, le réseau du cluster doit les supporter : k3s embarque un contrôleur de NetworkPolicies, actif par défaut.
flowchart LR
T["Traefik<br/>(kube-system)"] --> F["frontend"]
F --> A["api"]
A --> D["db<br/>port 5432"]
F -. "bloqué" .-> D
X["autre Pod"] -. "bloqué" .-> A
On commence par tout interdire en entrée, puis on autorise chaque flux nécessaire :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {} # tous les Pods du namespace
policyTypes: ["Ingress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
spec:
podSelector:
matchLabels:
tier: db # Pods protégés
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels:
tier: api # source autorisée
ports:
- protocol: TCP
port: 5432
Les sources autorisées se désignent de trois façons :
| Sélecteur | Désigne |
|---|---|
podSelector |
Des Pods du même namespace, par leurs labels |
namespaceSelector |
Tous les Pods d'un namespace, par ses labels (par exemple kubernetes.io/metadata.name: kube-system) |
ipBlock |
Une plage d'adresses IP |
ET ou OU : une erreur fréquente
Dans une liste from, chaque tiret est une source distincte (OU). Un namespaceSelector et un podSelector écrits dans le même élément (sans nouveau tiret) se combinent en ET : le Pod doit satisfaire les deux.
Un filtrage du trafic sortant (Egress) est aussi possible. Il faut alors autoriser explicitement le DNS, faute de quoi les noms de Services ne sont plus résolus.
!!! tip "À retenir" - Un cluster est ouvert par défaut : on applique le moindre privilège à chaque couche. - Les Pods utilisent des ServiceAccounts ; on désactive le montage du jeton quand il est inutile. - RBAC : Role + RoleBinding (namespace), ClusterRole + ClusterRoleBinding (cluster). Pas de règle = refus. Test : kubectl auth can-i ... --as=.... - SecurityContext : non-root, allowPrivilegeEscalation: false, capabilities supprimées, système de fichiers en lecture seule si possible. - Pod Security Standards : privileged, baseline, restricted, appliqués par labels de namespace (enforce, warn, audit). - NetworkPolicy : on interdit tout, puis on autorise les flux nécessaires ; chaque tiret de from est un OU.
Pour vérifier votre compréhension¶
Pourquoi désactiver le montage automatique du jeton de ServiceAccount dans un Pod qui n'appelle pas l'API Kubernetes ?
Le jeton donne accès à l'API avec les droits du ServiceAccount. Si un attaquant prend le contrôle du conteneur, il pourrait l'utiliser. Sans besoin, mieux vaut ne pas le monter.
Un ServiceAccount n'a aucun RoleBinding. Peut-il lister les Pods ?
Non. RBAC refuse par défaut : sans règle explicite qui l'autorise, l'action est refusée.
Quelle différence entre un Role et un ClusterRole, et entre un RoleBinding et un ClusterRoleBinding ?
Un Role vit dans un namespace et ne donne des droits que dans celui-ci ; un ClusterRole vaut pour tout le cluster. Un RoleBinding attribue des droits dans un namespace, un ClusterRoleBinding sur tout le cluster.
Un Pod ne démarre pas, avec une image qui s'exécute en root et runAsNonRoot: true. Que faire ?
Utiliser une image conçue pour tourner sans root (par exemple nginx-unprivileged), ou définir un utilisateur non-root dans l'image. Désactiver runAsNonRoot affaiblirait la sécurité.
Quelle différence entre les modes enforce et warn d'un niveau Pod Security ?
enforce refuse les Pods non conformes ; warn les accepte mais affiche un avertissement. warn sert à mesurer l'écart avant d'imposer le niveau.
Pourquoi poser d'abord une politique « deny all » avant les autorisations ?
Dès qu'un Pod est sélectionné par une politique, ce qui n'est pas autorisé est refusé. En partant de « tout interdit », on obtient une liste de flux autorisés explicite : toute autre communication est bloquée.
Quelle est la limite d'une politique qui ne filtre que le trafic entrant ?
Elle ne contrôle pas ce que les Pods protégés peuvent envoyer : un Pod compromis peut encore contacter d'autres Pods ou l'extérieur. Il faut aussi filtrer le trafic sortant.