AppArmor et Seccomp sont deux mécanismes du noyau Linux qui limitent ce qu’un conteneur peut faire, même si son processus s’exécute en root. Là où RBAC contrôle l’accès aux ressources Kubernetes, AppArmor et Seccomp contrôlent l’accès aux appels système et aux fichiers du nœud. Ce guide se concentre sur leur application dans Kubernetes — pour les bases Linux, consultez le guide AppArmor Linux.
| Mécanisme | Contrôle principal | Exemples de restrictions |
|---|---|---|
| Seccomp | Appels système (syscalls) | Bloquer unshare, mount, ptrace, setuid |
| AppArmor | Fichiers, capacités, réseau, comportements | Interdire /etc en écriture, bloquer ptrace, limiter le réseau |
Prérequis
Section intitulée « Prérequis »- Cluster Kubernetes 1.28+ (kubeadm, k3s, ou managé)
- Nœuds Linux avec AppArmor activé (
aa-enabled→ yes) - Accès
kubectlavec droits de créer des pods
Seccomp — Filtrer les appels système
Section intitulée « Seccomp — Filtrer les appels système »Seccomp (SECure COMPuting) restreint les appels système (syscalls) qu’un processus peut effectuer. Sans profil, un conteneur peut appeler n’importe quel syscall Linux — c’est une large surface d’attaque.
Les trois types de profil Seccomp
Section intitulée « Les trois types de profil Seccomp »| Type | Description | Quand l’utiliser |
|---|---|---|
Unconfined | Aucune restriction (défaut historique) | Débogage uniquement |
RuntimeDefault | Profil par défaut du runtime conteneur | Production — point de départ universel |
Localhost | Profil JSON personnalisé sur le nœud | Quand RuntimeDefault est trop restrictif |
Appliquer RuntimeDefault à un Pod
Section intitulée « Appliquer RuntimeDefault à un Pod »apiVersion: v1kind: Podmetadata: name: app-seccompspec: securityContext: seccompProfile: type: RuntimeDefault # Profil du runtime (containerd/cri-o) containers: - name: app image: nginx:1.25-alpineUtiliser un profil Seccomp personnalisé (Localhost)
Section intitulée « Utiliser un profil Seccomp personnalisé (Localhost) »Si une application utilise des syscalls non couverts par RuntimeDefault, créez un profil JSON sur chaque nœud :
# Répertoire attendu par kubeletsudo mkdir -p /var/lib/kubelet/seccomp/profiles
# Profil personnalisésudo tee /var/lib/kubelet/seccomp/profiles/myapp.json > /dev/null <<'EOF'{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": ["read", "write", "close", "fstat", "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "ioctl", "access", "execve", "exit_group", "openat", "newfstatat", "pread64", "prlimit64"], "action": "SCMP_ACT_ALLOW" } ]}EOFapiVersion: v1kind: Podmetadata: name: app-seccomp-customspec: securityContext: seccompProfile: type: Localhost localhostProfile: profiles/myapp.json # Relatif à /var/lib/kubelet/seccomp/ containers: - name: app image: myapp:latestVérifier le profil Seccomp d’un pod
Section intitulée « Vérifier le profil Seccomp d’un pod »# Via kubectlkubectl get pod <pod-name> -o jsonpath='{.spec.securityContext.seccompProfile}'
# Depuis le nœud (PID du conteneur)cat /proc/<pid>/status | grep Seccomp# 0 = Unconfined, 1 = Strict, 2 = Filter (profil en place)AppArmor — Profils de contrôle d’accès
Section intitulée « AppArmor — Profils de contrôle d’accès »AppArmor restreint l’accès d’un processus aux fichiers, répertoires, capabilities et appels réseau via des profils déclaratifs chargés dans le noyau.
Vérifier AppArmor sur les nœuds
Section intitulée « Vérifier AppArmor sur les nœuds »# Sur le nœud (via ssh ou node debug)aa-enabled # yes/noaa-status # Lister les profils chargésapparmor_parser -v # Version
# Profils disponiblesls /etc/apparmor.d/cat /sys/kernel/security/apparmor/profilesStructure d’un profil AppArmor
Section intitulée « Structure d’un profil AppArmor »#include <tunables/global>
profile k8s-app-readonly flags=(attach_disconnected) { #include <abstractions/base>
# Autoriser la lecture/exécution du binaire /usr/bin/myapp rix,
# Lecture seule sur /etc /etc/** r,
# Lecture/écriture sur /tmp uniquement /tmp/** rw,
# Interdire tout accès réseau deny network,
# Interdire ptrace (empêche les outils d'introspection) deny ptrace,}Charger un profil sur le nœud
Section intitulée « Charger un profil sur le nœud »# Charger en mode enforcesudo apparmor_parser -r -W /etc/apparmor.d/k8s-app-readonly
# Vérifier qu'il est chargésudo aa-status | grep k8s-app-readonlyAppliquer AppArmor à un Pod — syntaxe actuelle (v1.30+)
Section intitulée « Appliquer AppArmor à un Pod — syntaxe actuelle (v1.30+) »Depuis Kubernetes 1.30, AppArmor se configure via securityContext.appArmorProfile :
apiVersion: v1kind: Podmetadata: name: app-apparmorspec: securityContext: appArmorProfile: type: Localhost localhostProfile: k8s-app-readonly # Nom du profil chargé sur le nœud containers: - name: app image: nginx:1.25-alpine securityContext: appArmorProfile: # On peut surcharger par conteneur type: RuntimeDefault # Utiliser le profil par défaut du runtimeLes trois types disponibles pour appArmorProfile.type :
| Type | Signification |
|---|---|
Unconfined | Pas de profil AppArmor (déconseillé) |
RuntimeDefault | Profil par défaut du runtime (docker-default) |
Localhost | Profil spécifique chargé sur le nœud |
Priorité Pod vs conteneur
Section intitulée « Priorité Pod vs conteneur »AppArmor et Seccomp peuvent être définis au niveau Pod et au niveau container. Le champ container prend toujours le dessus :
| Mécanisme | Niveau Pod | Niveau container | Priorité |
|---|---|---|---|
| Seccomp | spec.securityContext.seccompProfile | spec.containers[].securityContext.seccompProfile | Container > Pod |
| AppArmor | spec.securityContext.appArmorProfile | spec.containers[].securityContext.appArmorProfile | Container > Pod |
Cela permet d’appliquer un profil par défaut au Pod et d’en surcharger un pour un conteneur spécifique (init container, sidecar).
Syntaxe legacy — annotations (avant v1.30)
Section intitulée « Syntaxe legacy — annotations (avant v1.30) »Pour les clusters plus anciens ou si vous devez lire des manifests legacy :
apiVersion: v1kind: Podmetadata: name: app-apparmor-legacy annotations: # Format : container.apparmor.security.beta.kubernetes.io/<container-name> container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-app-readonly # Autres valeurs possibles : runtime/default ou unconfinedspec: containers: - name: app image: nginx:1.25-alpineCombiner AppArmor et Seccomp
Section intitulée « Combiner AppArmor et Seccomp »Ce sont deux mécanismes complémentaires — ils s’appliquent en même temps :
apiVersion: v1kind: Podmetadata: name: pod-hardenedspec: securityContext: seccompProfile: type: RuntimeDefault # Seccomp : filtrer les syscalls runAsNonRoot: true runAsUser: 1000 containers: - name: app image: nginx:1.25-alpine securityContext: appArmorProfile: type: RuntimeDefault # AppArmor : restreindre l'accès fichiers/réseau allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]Ce pod applique une défense en profondeur :
- Seccomp
RuntimeDefault: 300+ syscalls autorisés, ~150 bloqués - AppArmor
RuntimeDefault: profildocker-default(ou équivalent containerd) capabilities: drop: ALL: zéro capability LinuxreadOnlyRootFilesystem: true: système de fichiers immuable
Vérifier l’application des profils
Section intitulée « Vérifier l’application des profils »# Vérifier les profils AppArmor actifs sur un nœudssh node01sudo aa-status
# Confirmer le profil d'un conteneur (via le PID)# 1. Trouver le PID du conteneur sur le nœudcrictl inspect <container-id> | jq '.info.pid'# 2. Lire le profilcat /proc/<pid>/attr/current
# Tester qu'un profil bloque bien (mode complain → enforce)sudo aa-complain /etc/apparmor.d/k8s-app-readonly # Loggue sans bloquersudo aa-enforce /etc/apparmor.d/k8s-app-readonly # Bloque effectivementAppliquer à grande échelle avec PodSecurityAdmission
Section intitulée « Appliquer à grande échelle avec PodSecurityAdmission »Le profil restricted de la Pod Security Admission ne mutate pas les Pods et n’injecte pas RuntimeDefault. Il refuse les Pods dont seccompProfile.type vaut Unconfined. Pour que RuntimeDefault s’applique par défaut aux Pods qui ne déclarent rien, activez seccompDefault: true côté kubelet.
apiVersion: v1kind: Namespacemetadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/enforce-version: latest pod-security.kubernetes.io/warn: restricted pod-security.kubernetes.io/warn-version: latest pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/audit-version: latestLes Pod Security Standards encadrent aussi AppArmor : avec restricted, seuls RuntimeDefault et Localhost sont autorisés. Utilisez Kyverno ou Gatekeeper si vous voulez aller plus loin — par exemple exiger explicitement un profil Localhost ou gérer des exceptions par namespace :
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: require-apparmor-profilespec: validationFailureAction: Enforce rules: - name: check-apparmor-profile match: any: - resources: kinds: [Pod] validate: message: "Un profil AppArmor RuntimeDefault ou Localhost est requis" anyPattern: - spec: securityContext: appArmorProfile: type: RuntimeDefault - spec: securityContext: appArmorProfile: type: LocalhostDépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Pod reste Pending | Profil AppArmor inexistant sur le nœud | aa-status sur le nœud, vérifier le nom du profil |
Error: failed to create containerd task | Profil non chargé | apparmor_parser -r /etc/apparmor.d/<profil> |
| Application crashe avec Seccomp | Syscall bloqué | Utiliser un profil d’audit (SCMP_ACT_LOG) ou complain mode AppArmor, lire dmesg | grep KILL et journalctl -k |
| Annotation AppArmor ignorée | Kubernetes ≥ 1.30 avec securityContext conflictuel | securityContext prime sur l’annotation deprecated |
aa-enabled retourne no | AppArmor non activé dans le kernel | Vérifier GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor" |
Limitations
Section intitulée « Limitations »- Linux uniquement : Seccomp et AppArmor ne fonctionnent pas sur les nœuds Windows
- Support du runtime : le profil
RuntimeDefaultdépend de l’implémentation containerd/CRI-O du nœud - Localhost = distribution manuelle : le profil JSON (Seccomp) ou AppArmor doit être présent sur tous les nœuds où le Pod peut être schedulé — complexité opérationnelle en cluster hétérogène
- Environnements managés (EKS, GKE, AKS) : l’accès SSH aux nœuds pour charger des profils est souvent limité ou impossible
privileged: trueannule tout : les conteneurs privilégiés ignorent Seccomp et AppArmor
Testez vos connaissances
Section intitulée « Testez vos connaissances »Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- Seccomp filtre les appels système ;
RuntimeDefaultsuffit pour 95% des workloads - AppArmor contrôle l’accès aux fichiers, capabilities et réseau via des profils nommés
- Sans
seccompDefault: truecôté kubelet, un Pod sansseccompProfileresteUnconfined - La PSA ne mutate pas les Pods : elle valide et rejette ;
seccompDefaultest distinct - Le niveau container prend le dessus sur le niveau Pod pour AppArmor et Seccomp
- Depuis Kubernetes 1.30 : utiliser
securityContext.appArmorProfile(annotations dépréciées) - Les deux mécanismes sont complémentaires — les appliquer ensemble pour une défense en profondeur
- Le profil doit être chargé sur chaque nœud où le pod peut s’exécuter