Aller au contenu
Conteneurs & Orchestration medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

AppArmor et Seccomp dans Kubernetes - Profils de sécurité

12 min de lecture

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écanismeContrôle principalExemples de restrictions
SeccompAppels système (syscalls)Bloquer unshare, mount, ptrace, setuid
AppArmorFichiers, capacités, réseau, comportementsInterdire /etc en écriture, bloquer ptrace, limiter le réseau
  • Cluster Kubernetes 1.28+ (kubeadm, k3s, ou managé)
  • Nœuds Linux avec AppArmor activé (aa-enabled → yes)
  • Accès kubectl avec droits de créer des pods

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.

TypeDescriptionQuand l’utiliser
UnconfinedAucune restriction (défaut historique)Débogage uniquement
RuntimeDefaultProfil par défaut du runtime conteneurProduction — point de départ universel
LocalhostProfil JSON personnalisé sur le nœudQuand RuntimeDefault est trop restrictif
pod-seccomp.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-seccomp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # Profil du runtime (containerd/cri-o)
containers:
- name: app
image: nginx:1.25-alpine

Utiliser 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 :

Fenêtre de terminal
# Répertoire attendu par kubelet
sudo 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"
}
]
}
EOF
pod-seccomp-custom.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-seccomp-custom
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/myapp.json # Relatif à /var/lib/kubelet/seccomp/
containers:
- name: app
image: myapp:latest
Fenêtre de terminal
# Via kubectl
kubectl 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 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.

Fenêtre de terminal
# Sur le nœud (via ssh ou node debug)
aa-enabled # yes/no
aa-status # Lister les profils chargés
apparmor_parser -v # Version
# Profils disponibles
ls /etc/apparmor.d/
cat /sys/kernel/security/apparmor/profiles
#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,
}
Fenêtre de terminal
# Charger en mode enforce
sudo apparmor_parser -r -W /etc/apparmor.d/k8s-app-readonly
# Vérifier qu'il est chargé
sudo aa-status | grep k8s-app-readonly

Appliquer 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 :

pod-apparmor-v130.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-apparmor
spec:
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 runtime

Les trois types disponibles pour appArmorProfile.type :

TypeSignification
UnconfinedPas de profil AppArmor (déconseillé)
RuntimeDefaultProfil par défaut du runtime (docker-default)
LocalhostProfil spécifique chargé sur le nœud

AppArmor et Seccomp peuvent être définis au niveau Pod et au niveau container. Le champ container prend toujours le dessus :

MécanismeNiveau PodNiveau containerPriorité
Seccompspec.securityContext.seccompProfilespec.containers[].securityContext.seccompProfileContainer > Pod
AppArmorspec.securityContext.appArmorProfilespec.containers[].securityContext.appArmorProfileContainer > 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).

Pour les clusters plus anciens ou si vous devez lire des manifests legacy :

pod-apparmor-legacy.yaml
apiVersion: v1
kind: Pod
metadata:
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 unconfined
spec:
containers:
- name: app
image: nginx:1.25-alpine

Ce sont deux mécanismes complémentaires — ils s’appliquent en même temps :

pod-hardened.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-hardened
spec:
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 : profil docker-default (ou équivalent containerd)
  • capabilities: drop: ALL : zéro capability Linux
  • readOnlyRootFilesystem: true : système de fichiers immuable
Fenêtre de terminal
# Vérifier les profils AppArmor actifs sur un nœud
ssh node01
sudo aa-status
# Confirmer le profil d'un conteneur (via le PID)
# 1. Trouver le PID du conteneur sur le nœud
crictl inspect <container-id> | jq '.info.pid'
# 2. Lire le profil
cat /proc/<pid>/attr/current
# Tester qu'un profil bloque bien (mode complain → enforce)
sudo aa-complain /etc/apparmor.d/k8s-app-readonly # Loggue sans bloquer
sudo aa-enforce /etc/apparmor.d/k8s-app-readonly # Bloque effectivement

Appliquer à 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.

namespace-pss.yaml
apiVersion: v1
kind: Namespace
metadata:
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: latest

Les 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 :

kyverno-require-apparmor.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-apparmor-profile
spec:
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: Localhost
SymptômeCause probableSolution
Pod reste PendingProfil AppArmor inexistant sur le nœudaa-status sur le nœud, vérifier le nom du profil
Error: failed to create containerd taskProfil non chargéapparmor_parser -r /etc/apparmor.d/<profil>
Application crashe avec SeccompSyscall bloquéUtiliser un profil d’audit (SCMP_ACT_LOG) ou complain mode AppArmor, lire dmesg | grep KILL et journalctl -k
Annotation AppArmor ignoréeKubernetes ≥ 1.30 avec securityContext conflictuelsecurityContext prime sur l’annotation deprecated
aa-enabled retourne noAppArmor non activé dans le kernelVérifier GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor"
  • Linux uniquement : Seccomp et AppArmor ne fonctionnent pas sur les nœuds Windows
  • Support du runtime : le profil RuntimeDefault dé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: true annule tout : les conteneurs privilégiés ignorent Seccomp et AppArmor

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
8 min.
70% requis

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

  • Seccomp filtre les appels système ; RuntimeDefault suffit pour 95% des workloads
  • AppArmor contrôle l’accès aux fichiers, capabilities et réseau via des profils nommés
  • Sans seccompDefault: true côté kubelet, un Pod sans seccompProfile reste Unconfined
  • La PSA ne mutate pas les Pods : elle valide et rejette ; seccompDefault est 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

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn