Seccomp filtre les appels système qu'un conteneur peut adresser au noyau Linux, et un profil RuntimeDefault en bloque déjà une quarantaine sans qu'aucune application classique ne s'en aperçoive. C'est la protection la moins chère du durcissement Kubernetes : une ligne de securityContext, aucun fichier à distribuer, aucun redémarrage de nœud. Ce guide vous montre comment l'appliquer, comment écrire un profil sur mesure quand le profil du runtime ne suffit pas, et surtout comment prouver que le filtre agit, parce qu'un manifeste qui déclare un profil ne garantit pas que le noyau l'applique.
Prérequis : un cluster Kubernetes et kubectl. Contrairement à
AppArmor,
seccomp fonctionne sur des nœuds qui sont eux-mêmes des conteneurs, kind ou
k3d compris : tout ce guide se pratique sur un poste de travail.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Ce qu'est un appel système et pourquoi le filtrer réduit la surface d'attaque
- Appliquer
RuntimeDefaultà un Pod, la forme à viser par défaut - Écrire un profil
Localhostet comprendre ce qu'il coûte à maintenir - Vérifier dans
/procque le filtre est réellement en place - Reconnaître ce qui annule seccomp sans émettre le moindre avertissement
Qu'est-ce qu'un appel système, et pourquoi le filtrer ?
Section intitulée « Qu'est-ce qu'un appel système, et pourquoi le filtrer ? »Un appel système, ou syscall, est la seule porte par laquelle un programme demande quelque chose au noyau Linux : ouvrir un fichier, écrire sur une socket, créer un processus, monter un système de fichiers. Un noyau x86_64 récent en expose plus de 300. Une application web classique en utilise quelques dizaines, et ignore tout le reste.
Ce reste est précisément le problème. Un conteneur sans profil seccomp peut appeler les 300, y compris ceux qui ne servent qu'à l'administration du noyau ou à l'évasion d'un bac à sable : mount, unshare, ptrace, kexec_load, perf_event_open. Aucun d'eux n'est utile à une application métier, et chacun a déjà servi de marche à une élévation de privilèges. Seccomp (pour SECure COMPuting) installe un filtre dans le noyau qui refuse les appels non autorisés, et ce filtre s'applique au processus avant même que le code de l'application démarre.
Les trois types de profil seccomp
Section intitulée « Les trois types de profil seccomp »Ces trois types ne sont pas trois crans d'un même curseur. RuntimeDefault est le seul à viser par défaut, les deux autres sont des exceptions qui se justifient. Unconfined désactive complètement le filtre et n'a sa place que le temps d'identifier l'appel qui bloque une application. Localhost impose de distribuer et de maintenir un fichier JSON sur chaque nœud, ce qui est une dette opérationnelle réelle.
| Type | Description | Quand l'utiliser |
|---|---|---|
Unconfined | Aucune restriction, défaut historique | Débogage uniquement |
RuntimeDefault | Profil livré par le runtime de conteneurs | Production, point de départ universel |
Localhost | Profil JSON personnalisé, présent sur le nœud | Quand RuntimeDefault est trop restrictif |
Le profil RuntimeDefault est celui que le runtime embarque, containerd ou CRI-O selon votre cluster. La documentation Docker, dont ce profil est issu, indique qu'il « désactive environ 44 appels système sur plus de 300 ». Autrement dit, une application classique ne verra aucune différence, et c'est exactement ce qui rend son activation généralisable sans campagne de tests.
Appliquer RuntimeDefault à un Pod
Section intitulée « Appliquer RuntimeDefault à un Pod »Déclaré au niveau spec.securityContext, le profil s'applique à tous les conteneurs du Pod, init containers et sidecars compris. C'est la forme à privilégier : une seule déclaration, aucun conteneur oublié lors d'un ajout ultérieur.
apiVersion: v1kind: Podmetadata: name: app-seccompspec: securityContext: seccompProfile: type: RuntimeDefault # Profil du runtime (containerd/cri-o) containers: - name: app image: nginx:1.31-alpine@sha256:72ba65eb42c10344912a84ff42408db7d34f2feb642204570ab8fc5ffd29f1d3Pod ou conteneur : lequel l'emporte ?
Section intitulée « Pod ou conteneur : lequel l'emporte ? »Les deux niveaux existent, et leur priorité est celle qu'on attend : le securityContext du conteneur prime sur celui du Pod. Un Pod qui déclare RuntimeDefault pour tout le monde peut donc héberger un conteneur qui repasse en Unconfined, et c'est une porte de sortie légitime pour un sidecar de débogage.
spec: securityContext: seccompProfile: type: RuntimeDefault # s'applique à tous les conteneurs containers: - name: app # hérite de RuntimeDefault image: registry.example.net/facturation:2.3.1 - name: debug image: registry.example.net/outils:1.4.0 securityContext: seccompProfile: type: Unconfined # cette surcharge l'emporteC'est aussi la première chose à regarder quand un Pod semble protégé et ne l'est pas : la déclaration du Pod ne dit rien de ce que chaque conteneur applique. Une revue de manifeste doit descendre au niveau conteneur, et une politique d'admission doit tester les deux niveaux dans cet ordre.
Écrire et déployer un profil Localhost
Section intitulée « Écrire et déployer un profil Localhost »Quand une application a besoin d'appels que RuntimeDefault refuse, ou quand vous voulez au contraire une liste blanche stricte, le profil passe par un fichier JSON déposé sur le nœud. Le champ décisif est defaultAction : avec SCMP_ACT_ERRNO, tout appel absent de la liste names est refusé avec EPERM.
# Répertoire attendu par le kubeletsudo mkdir -p /var/lib/kubelet/seccomp/profiles
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" } ]}EOFCet exemple est volontairement minimal et ne suffira à presque aucune application réelle : c'est un point de départ à compléter en observant les refus, pas un profil à copier en production. Trois actions se rencontrent dans ces fichiers, et elles n'ont pas le même usage : SCMP_ACT_ALLOW autorise, SCMP_ACT_ERRNO refuse en rendant une erreur au programme, et SCMP_ACT_LOG laisse passer en journalisant, ce qui en fait l'outil de mise au point d'un profil avant de le durcir.
Le manifeste désigne ensuite le fichier par un chemin relatif à /var/lib/kubelet/seccomp/ :
apiVersion: v1kind: Podmetadata: name: app-seccomp-customspec: securityContext: seccompProfile: type: Localhost localhostProfile: profiles/myapp.json containers: - name: app image: registry.example.net/facturation:2.3.1Si le fichier n'existe pas sur le nœud qui a récolté le Pod, le kubelet refuse de démarrer le conteneur au lieu de le lancer sans protection. Le comportement est le bon, mais il transforme la présence du profil sur tous les nœuds en prérequis opérationnel : dans un cluster hétérogène, un Pod qui atterrit sur le mauvais nœud reste bloqué.
Comment vérifier qu'un profil s'applique vraiment ?
Section intitulée « Comment vérifier qu'un profil s'applique vraiment ? »Ne concluez jamais depuis le manifeste. La première commande relit ce que le Pod déclare, ce qui ne prouve rien sur l'état du processus. La seconde lit l'attribut réel dans /proc, et c'est la seule qui fait foi. Le chiffre renvoyé est celui du noyau Linux, pas de Kubernetes.
# Ce que le manifeste déclarekubectl get pod app-seccomp -o jsonpath='{.spec.securityContext.seccompProfile}'
# Ce que le noyau applique, lu depuis le conteneur lui-mêmekubectl exec app-seccomp -- grep Seccomp /proc/1/statusSeccomp: 2Seccomp_filters: 1La valeur qui compte est 2, pour « filtre en place ». 0 signifie Unconfined et 1 correspond au mode strict historique, que Kubernetes n'utilise pas. Le contraste se mesure sur deux Pods identiques à un champ près, sur le même cluster :
| Pod | securityContext.seccompProfile | /proc/1/status |
|---|---|---|
| avec | type: RuntimeDefault | Seccomp: 2 et Seccomp_filters: 1 |
| sans | aucun | Seccomp: 0 |
Si le Pod n'a pas de shell, ou si vous enquêtez sur un conteneur déjà arrêté, il faut passer par le nœud et retrouver le processus par son identifiant :
crictl inspect <container-id> | jq '.info.pid'grep Seccomp /proc/<pid>/statusCe qui annule seccomp sans prévenir
Section intitulée « Ce qui annule seccomp sans prévenir »C'est la raison pour laquelle la vérification par /proc n'est pas une coquetterie. Un 0 sur un Pod qui déclare pourtant RuntimeDefault ne signale presque jamais une faute de frappe : il signale un conteneur privilégié, et donc un durcissement annulé en silence. Le même piège vaut pour AppArmor, dont les profils sont ignorés dans les mêmes conditions.
Seccomp et AppArmor : deux filtres, deux surfaces
Section intitulée « Seccomp et AppArmor : deux filtres, deux surfaces »Les deux mécanismes vivent dans le noyau Linux et se ressemblent de loin, mais ils ne filtrent pas la même chose et ne se remplacent pas. Les appliquer ensemble est ce que recouvre l'expression défense en profondeur.
| Mécanisme | Ce qu'il contrôle | Exemples |
|---|---|---|
| Seccomp | Appels système | Bloquer unshare, mount, ptrace, setuid |
| AppArmor | Fichiers, capacités, réseau | Interdire /etc en écriture, limiter le réseau |
Une différence pratique les sépare, et elle décide de ce que vous pouvez éprouver en lab : AppArmor exige un nœud dont le noyau l'active, ce qui exclut les clusters dont les nœuds sont des conteneurs, tandis que seccomp fonctionne sur ces mêmes nœuds. Le détail des profils AppArmor, de leur chargement et de leur vérification vit sur sa page dédiée.
Appliquer seccomp à grande échelle
Section intitulée « Appliquer seccomp à grande échelle »Deux leviers existent, et on les confond souvent. seccompDefault: true côté kubelet mutate le comportement par défaut : tout Pod sans déclaration reçoit RuntimeDefault. La Pod Security Admission, elle, ne mutate rien : son profil restricted refuse les Pods qui déclarent Unconfined, et laisse passer ceux qui ne déclarent rien.
apiVersion: v1kind: Namespacemetadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/enforce-version: latestLa combinaison qui tient en production est donc la somme des deux : seccompDefault pour que l'absence de déclaration devienne protectrice, et la PSA pour que personne ne revienne en arrière explicitement. Pour aller plus loin, exiger un profil Localhost précis par exemple, il faut une politique d'admission écrite avec Kyverno ou Gatekeeper.
Dépannage
Section intitulée « Dépannage »Le symptôme le plus déroutant est le premier du tableau : l'application démarre puis meurt sans message clair, parce qu'un appel qu'elle a besoin d'émettre est refusé. La méthode est toujours la même, repasser le profil en mode journalisation pour voir ce qui est refusé avant de durcir à nouveau.
| Symptôme | Cause probable | Solution |
|---|---|---|
| L'application plante au démarrage | Un appel système nécessaire est refusé | Passer le profil en SCMP_ACT_LOG, lire journalctl -k et dmesg | grep -i seccomp |
| Le conteneur ne démarre pas du tout | Profil Localhost absent du nœud d'atterrissage | Vérifier le nœud avec kubectl get pod -o wide, déposer le fichier, ou revenir à RuntimeDefault |
Seccomp: 0 alors que le Pod déclare un profil | Conteneur en privileged: true | Retirer privileged, le filtre est annulé sans avertissement |
| Le profil semble ignoré | Surcharge au niveau conteneur | Le securityContext du conteneur prime sur celui du Pod |
| Le Pod est refusé à l'admission | Unconfined explicite dans un namespace restricted | Déclarer RuntimeDefault, ou justifier une exception de namespace |
Limites de seccomp
Section intitulée « Limites de seccomp »Quatre limites expliquent pourquoi seccomp n'est pas déployé partout, alors qu'il coûte presque rien. Les deux premières relèvent de l'infrastructure, les deux dernières de vos propres manifestes.
- Linux uniquement : rien de tout cela ne s'applique aux nœuds Windows.
- Dépendance au runtime : le contenu exact de
RuntimeDefaultvient de containerd ou CRI-O, il n'est pas défini par Kubernetes. Localhostse distribue à la main : le fichier doit exister sur tous les nœuds où le Pod peut atterrir, et les environnements managés limitent souvent l'accès aux nœuds.privilegedannule tout : un seul conteneur privilégié suffit à vider la mesure de son sens.
Une limite conceptuelle s'ajoute, plus importante que les quatre autres : seccomp filtre des appels système, pas des intentions. Un processus autorisé à openat peut ouvrir n'importe quel fichier auquel ses permissions lui donnent accès. C'est AppArmor, SELinux ou un bac à sable de runtime qui répondent à cette question-là.
Ce qui tombe à la CKS
Section intitulée « Ce qui tombe à la CKS »Seccomp relève du domaine System Hardening, qui pèse 15 % de l'épreuve. Trois points sont discriminants, et ce sont rarement les plus évidents : la priorité du niveau conteneur sur le niveau Pod, la différence entre seccompDefault côté kubelet et le profil restricted de la Pod Security Admission, et le fait qu'un conteneur privilégié annule le filtre. Savoir écrire le champ seccompProfile ne suffit pas, l'examen porte sur ce qui se passe quand deux mécanismes se contredisent.
Testez vos connaissances
Section intitulée « Testez vos connaissances »Les questions portent sur ce qui décide vraiment en production comme à l'examen : le profil par défaut en l'absence de déclaration, la priorité entre les deux niveaux, et ce que la Pod Security Admission fait réellement. Une réponse ratée désigne la section à relire.
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 qu'un conteneur adresse au noyau, environ 44 bloqués sur 300 avec
RuntimeDefault RuntimeDefaultest le point de départ universel : aucune application classique ne le remarque- Sans
seccompDefault: truecôté kubelet, un Pod qui ne déclare rien resteUnconfined - La PSA ne mutate pas : elle rejette
Unconfinedexplicite, elle n'ajoute jamais de profil - Le niveau conteneur prime sur le niveau Pod, y compris pour revenir à
Unconfined - Un profil
Localhostdoit exister sur chaque nœud d'atterrissage possible privileged: trueannule le filtre en silence : seul/proc/1/statusfait foi
Mettre en pratique
Section intitulée « Mettre en pratique »Un profil ne se juge pas à son JSON mais à ce qu'il refuse. Ce lab vous fait écrire un profil seccomp qui interdit une famille d'appels système, l'appliquer par le securityContext, puis prouver que le confinement tient depuis l'intérieur du conteneur : l'appel interdit échoue, et tout le reste continue de fonctionner.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Kyverno : Exiger un profil seccomp à l'admission, là où la Pod Security Admission s'arrête.
- Falco : Détecter les appels système suspects que votre profil laisse passer.
- Runtime Sandboxes : L'isolation par gVisor ou Kata, un cran au-dessus d'un filtre d'appels système.