Skip to main content

Kubernetes securityContext : les capacités Linux dans Kubernetes

Écrit par
Kubernetes securityContext

26 janvier 2021

0 minutes de lecture

Il y a bien longtemps, les systèmes d’exploitation Unix reposaient sur un modèle d’autorisations relativement simple : on était soit un utilisateur standard, soit root, le superutilisateur qui dispose de tous les droits.

Les utilisateurs standard pouvaient certes obtenir des autorisations élevées sur des fichiers ou des répertoires, mais presque toutes les fonctions du noyau étaient réservées à root. Si une application avait besoin d’un seul appel système du noyau pour fonctionner, il fallait lui accorder les privilèges SUID — ce qui conférait de fait les privilèges root à son processus lorsqu’il était lancé par un utilisateur standard.

Cette approche fonctionnait plutôt bien à l’époque où les machines physiques étaient utilisées par un nombre relativement restreint de personnes. Mais cette granularité grossière n’était plus adaptée au monde moderne. Pour offrir davantage de flexibilité et de sécurité, les développeurs du noyau Linux ont mis au point une solution beaucoup plus précise : regrouper les appels système du noyau en capacités et permettre d’attribuer ces capacités à chaque processus. Ainsi, si une application avait besoin d’un seul appel système du noyau, il suffisait de lui attribuer la capacité correspondante, limitant ainsi les risques pour la sécurité du système.

Gérer les privilèges dans les conteneurs Kubernetes

Alors, comment cela fonctionne-t-il dans les conteneurs ?

En réalité, un conteneur n’est qu’un processus exécuté sur le système, isolé au moyen des cgroups et des espaces de noms du noyau. Les capacités peuvent donc lui être attribuées comme à n’importe quel autre processus. C’est le runtime de conteneurs qui s’en charge lorsqu’il crée le conteneur.

En général, le runtime de conteneurs attribue un ensemble de capacités par défaut au conteneur (vous pouvez consulter ici l’ensemble fourni par Docker) et permet également d’ajouter ou de supprimer des capacités. Si vous exécutez un conteneur avec l’option --privileged, vous lui accordez toutes les capacités. Si vous l’exécutez en tant que root, cela peut entraîner un grave problème de sécurité, un sujet que j’ai abordé dans un article précédent.

Définir les capacités d’un conteneur dans le securityContext de Kubernetes

Dans Kubernetes, le plan de contrôle utilise ces paramètres du runtime de conteneurs pour définir les capacités attribuées à notre conteneur au démarrage. L’utilisateur peut configurer les capacités à l’aide de différents paramètres dans la section securityContext du fichier YAML du conteneur. Voici à quoi ressemble cette configuration :

securityContext:
      capabilities:
        drop:
          - ALL
        add: [“NET_ADMIN”]

Dans cet exemple, nous supprimerions toutes les capacités, puis ajouterions CAP_NET_ADMIN. Si la section capabilities du securityContext est vide, nous obtenons l’ensemble de capacités par défaut défini par le runtime de conteneurs. Cet ensemble est généralement assez généreux et peut largement dépasser les besoins de notre application. Déployons un pod dans Kubernetes pour voir quelles capacités nous obtenons.

Commençons par créer un fichier YAML pour déployer un pod. Dans cette configuration, nous utilisons une image disponible sur Docker Hub, basée sur Alpine et dotée de l’outil capsh, qui nous permettra de voir les capacités de notre conteneur.

Notez que nous n’avons pas défini de section securityContext pour ce conteneur : tous les paramètres seront donc ceux du système par défaut. Rappelons que si aucun utilisateur n’est spécifié dans Kubernetes, le conteneur s’exécute avec l’utilisateur par défaut indiqué dans le Dockerfile utilisé pour le créer — souvent root.

% cat caps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: caps
  labels:
    app: caps
spec:
  containers:
  - name: caps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

% kubectl apply -f caps.yaml 
pod/caps created

Une fois le pod opérationnel, nous pouvons ouvrir un shell à l’intérieur et exécuter capsh pour vérifier les capacités :

% kubectl exec --stdin --tty caps -- ash 
/ # capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Supprimer des capacités dans le securityContext de Kubernetes

Nous constatons donc que, par défaut, nous nous exécutons en tant que root et disposons d’un grand nombre de capacités. Il s’agit de l’ensemble défini par défaut par le runtime de conteneurs. Essayons maintenant de supprimer l’une de ces capacités dans les paramètres de notre securityContext : nous allons retirer la possibilité de créer des nœuds de système de fichiers, c’est-à-dire CAP_MKNOD :

matt@Mattbook capabilities_testing % cat dropcaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dropcaps
  labels:
    app: dropcaps
spec:
  containers:
  - name: dropcaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop: ["MKNOD"]
  restartPolicy: Always

% kubectl apply -f dropcaps.yaml 
pod/dropcaps created

% kubectl exec --stdin --tty dropcaps -- ash
/ # capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Par rapport au premier pod, nous constatons que celui-ci ne dispose plus de la capacité cap_mknod. Nous pouvons également supprimer toutes les capacités dans le securityContext, et pas seulement une capacité à la fois :

matt@Mattbook capabilities_testing % cat nocaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nocaps
  labels:
    app: nocaps
spec:
  containers:
  - name: nocaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop:
          - ALL
  restartPolicy: Always

matt@Mattbook capabilities_testing % kubectl apply -f nocaps.yaml 
pod/nocaps created

matt@Mattbook capabilities_testing % kubectl exec --stdin --tty nocaps -- ash
/ # capsh --print
Current: =
Bounding set =
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Sans aucune capacité, certaines fonctions système échoueront. Essayons, par exemple, d’installer bash avec apk :

/ # apk add bash
(1/5) Installing ncurses-terminfo-base (6.1_p20180818-r1)
(2/5) Installing ncurses-terminfo (6.1_p20180818-r1)
(3/5) Installing ncurses-libs (6.1_p20180818-r1)
(4/5) Installing readline (7.0.003-r0)
(5/5) Installing bash (4.4.19-r1)
Executing bash-4.4.19-r1.post-install
ERROR: bash-4.4.19-r1.post-install: script exited with error 127
Executing busybox-1.28.4-r1.trigger
ERROR: busybox-1.28.4-r1.trigger: script exited with error 127
1 error; 13 MiB in 19 packages

Même si notre conteneur s’exécute en tant que root, l’installation du paquet bash échoue, car elle nécessite de définir des autorisations sur le système de fichiers. Or, nous avons supprimé cette capacité de notre conteneur. Si nous voulions empêcher quiconque d’installer des logiciels dans notre conteneur, la gestion des autorisations au moyen des capacités serait une solution.

capsh permet d’afficher les capacités de notre conteneur dans un format lisible, mais ce n’est pas le seul moyen de les connaître. Nous pouvons aussi accéder directement à ces informations dans le système de fichiers proc, sans installer de logiciel supplémentaire :

/ # cd /proc/1/
/proc/1 # cat status 
----truncated
CapPrm:0000000000000000
CapEff:0000000000000000
CapBnd:0000000000000000
CapAmb:0000000000000000
----truncated

Dans le fichier /proc/1/status, les capacités sont représentées sous forme de bitmap. Comme aucune capacité n’est activée dans ce conteneur, les valeurs sont toutes nulles. Voyons maintenant le même fichier dans le conteneur d’origine, où toutes les capacités sont activées :

/ # cd /proc/1/
/proc/1 # cat status 
----truncated
CapInh:00000000a80425fb
CapPrm:00000000a80425fb
CapEff:00000000a80425fb
CapBnd:00000000a80425fb
----truncated

Le résultat est moins lisible que celui de capsh, mais chaque bit correspond à une capacité particulière, comme indiqué dans l’en-tête du noyau correspondant.

Découvrez comment renforcer la sécurité de Kubernetes en supprimant les capacités par défaut d’un conteneur.

Ajouter des capacités dans le securityContext de Kubernetes

En plus de supprimer des capacités, nous pouvons aussi les rétablir. Reprenons notre exemple précédent et ajoutons une seule capacité aux paramètres du securityContext : CAP_MKNOD :

% cat addcaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: addcaps
  labels:
    app: addcaps
spec:
  containers:
  - name: addcaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop:
          - ALL
        add: ["MKNOD"]
  restartPolicy: Always

% kubectl create -f addcaps.yaml           
pod/addcaps created
% kubectl exec --stdin --tty addcaps -- ash
/ # capsh --print
Current: = cap_mknod+eip
Bounding set =cap_mknod
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Dans cet exemple, nous constatons que seule cette capacité est désormais activée.

Le principe du moindre privilège dans le securityContext de Kubernetes

Dans cet article, nous avons vu comment fonctionnent les capacités dans les conteneurs, comment les configurer dans le securityContext de Kubernetes et comment le runtime de conteneurs les gère.

Conformément au principe du moindre privilège, la meilleure pratique en matière de sécurité consiste à n’accorder à notre conteneur que les capacités dont il a réellement besoin. J’ai abordé ce sujet dans un article précédent. Il peut être surprenant de constater que la plupart des processus n’ont pas besoin de capacités du noyau : même lorsqu’ils nécessitent des autorisations élevées, celles-ci peuvent généralement être gérées au niveau des fichiers.

Commencez par supprimer toutes les capacités dans le securityContext, puis ajoutez uniquement celles dont vous avez besoin. Pour diagnostiquer les erreurs, vous pouvez consulter la sortie d’outils comme SELinux afin de repérer les capacités susceptibles d’en être la cause. N’oubliez pas non plus que les conteneurs Kubernetes peuvent s’exécuter en tant que root si vous ne spécifiez pas un autre utilisateur.

Appliquer les paramètres de capacités du securityContext dans Kubernetes

Pour garantir la définition des paramètres du securityContext, comme les capacités et l’exécution sans privilèges root, nous pouvons utiliser des contrôleurs d’admission dans notre cluster Kubernetes. Ils empêcheront le lancement de conteneurs qui ne respectent pas les paramètres de sécurité requis. Kubernetes intègre le contrôleur PodSecurityPolicy, qui permet d’appliquer les paramètres du securityContext.

Toutefois, veuillez noter que cette fonctionnalité sera obsolète à partir de la version 1.21, au profit de projets maintenus en externe, comme Open Policy Agent.

Nous devons également intégrer directement à notre processus de développement des mécanismes de visibilité et de correction pour ce type de paramètres de sécurité. Snyk peut analyser vos fichiers YAML Kubernetes, détecter les paramètres non sécurisés du securityContext concernant les capacités et d’autres configurations, et fournir des conseils de correction directement dans les workflows des développeurs. Cette fonctionnalité est disponible via la Snyk CLI et peut également être intégrée directement aux systèmes de gestion du code source et d’intégration continue :

% snyk iac test caps.yaml

Testing caps.yaml...

Infrastructure as code issues:
  ✗ Container is running with default set of capabilities [Medium Severity] [SNYK-CC-K8S-6] in Deployment
    introduced by input > spec > containers[caps] > securityContext > capabilities > drop

  ✗ Container is running without root user control [Medium Severity] [SNYK-CC-K8S-10] in Deployment
    introduced by input > spec > containers[caps] > securityContext > runAsNonRoot

  ✗ Container is running without memory limit [Low Severity] [SNYK-CC-K8S-4] in Deployment
    introduced by input > spec > containers[caps] > resources > limits > memory

  ✗ Container is running without cpu limit [Low Severity] [SNYK-CC-K8S-5] in Deployment
    introduced by input > spec > containers[caps] > resources > limits > cpu

  ✗ Container is running with writable root filesystem [Low Severity] [SNYK-CC-K8S-8] in Deployment
    introduced by input > spec > containers[caps] > securityContext > readOnlyRootFilesystem

  ✗ Container is running without AppArmor profile [Low Severity] [SNYK-CC-K8S-32] in Deployment
    introduced by metadata > annotations['container.apparmor.security.beta.kubernetes.io/caps']

  ✗ Container is running without liveness probe [Low Severity] [SNYK-CC-K8S-41] in Deployment
    introduced by spec > containers[caps] > livenessProbe

  ✗ Container could be running with outdated image [Low Severity] [SNYK-CC-K8S-42] in Deployment
    introduced by spec > containers[caps] > imagePullPolicy

Organization:      matt-jarvis-snyk
Type:              Kubernetes
Target file:       caps.yaml
Project name:      capabilities_testing
Open source:       no
Project path:      caps.yaml

Tested caps.yaml for known issues, found 8 issues
Exemple de code de contexte de sécurité Kubernetes montrant un conteneur configuré avec privileged: true et les capacités par défaut

Envie d’essayer cette fonctionnalité ? Créez votre compte gratuit dès aujourd’hui !

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature pypi spoof
Article

La Snyk AI Security Platform complète, gratuite pour les responsables de projets open source

Les responsables de projets open source croulent sous les signalements de vulnérabilités avérées et ont besoin d’aide pour les prioriser, les corriger et déployer les correctifs plus rapidement. Le Secure Developer Program de Snyk donne aux projets admissibles un accès gratuit à la Snyk AI Security Platform.

feature insights announcement
Blog

Compromission de la chaîne d’approvisionnement Node-gyp : un ver npm à propagation autonome dissimulé dans binding.gyp

Un nouveau ver npm détourne binding.gyp pour déclencher node-gyp lors de l’installation et permettre à des packages malveillants d’exécuter du code sans scripts de cycle de vie. Il vole des identifiants, s’installe durablement sur GitHub et se propage de mainteneur en mainteneur.

Article

Publication de versions malveillantes de node-ipc sur npm à la suite d’une compromission présumée du compte d’un mainteneur

Le 14 mai 2026, plusieurs versions malveillantes du célèbre package npm node-ipc ont été publiées sur le registre npm. Les informations publiques disponibles identifient actuellement node...