Combien de fois ai-je entendu dire : « Docker ce n’est pas secure ! »
Cela peut être vrai, à moins de bien configurer l’outil !

Je vous propose de nous plonger dans certaines fonctionnalités de Docker : l’activation des User Namespaces et la translation d’UID/GID

Public visé : Administrateurs systèmes, Utilisateurs Docker

Par Sébastien Aucouturier, Consultant DevOps @Objectif Libre

Sécuriser Docker au travers de la fonctionnalité userns-remap

Sans activer ces fonctionnalités, quel est le problème ?

Facilement, un utilisateur non root peut utiliser l’accès root d’un conteneur pour modifier l’hôte.
Voici comment procéder :

$ whoami
seb
$ echo "#mytest" >> /etc/hosts
bash: /etc/hosts: Permission denied

# Run a container and mount host directory /etc onto docker /root/etc
$ docker run --rm -v /etc:/root/etc -it busybox

# Make some change on /root/etc/hosts
root@34ef23438542:/# echo "#mytest" >> /root/etc/hosts

# Exit from the container
root@34ef23438542:/# exit

# Check /etc/hosts
$ cat /etc/hosts

Comme vous pouvez le voir, c’est incroyablement facile !
Mais avec l’option –userns-remap , Docker vous permet d’éviter cette faille de sécurité.

Petit rappel: principe de base d’un conteneur

Docker, comme toutes les solutions de conteneurisation, utilise le noyau du système d’exploitation de l’hôte, un conteneur n’embarque pas de Kernel personnalisé ou supplémentaire. Tous les conteneurs qui fonctionnent sur une machine partagent ce même noyau « hôte ». Et Docker utilise les fonctionnalités du Kernel Linux, comme les namespaces et cgroups pour cloisonner et isoler les différents conteneurs. Pour éviter l’écueil vu précédemment nous allons faire appel au USER Namespace et changer son comportement par défaut.

Activation des User Namespaces

# Create a user called "dockremap"
$ sudo adduser dockremap

# Setup subuid and subgid
$ sudo sh -c 'echo dockremap:500000:65536 > /etc/subuid'
$ sudo sh -c 'echo dockremap:500000:65536 > /etc/subgid'

Ensuite, nous allons ajouter l’option –userns-remap=default au démon docker. Avec un environnement systemctl pour gérer les services, le fichier à modifier est /etc/sysconfig/docker

OPTIONS="--userns-remap=default"

Ou bien paramétrer via le fichier de configuration : /etc/docker/daemon.json contenant :

{
"userns-remap" : "default"
}

N’oubliez pas de redémarrer le démon après avoir édité le fichier.

Remarques:

Si vous utilisez CentOS 7, les User Namespaces ne sont pas activés dans le noyau par défaut. Vous pouvez l’activer en exécutant la commande suivante puis en redémarrant le système.

$ sudo grubby --args="user_namespace.enable=1" --update-kernel=/boot/vmlinuz-3.10.0-XXX.XX.X.el7.x86_64

$ sudo echo 31096 > /proc/sys/user/max_user_namespaces

Vérification du bon fonctionnement des User Namespaces

Si tout est correctement configuré, vous ne devriez plus pouvoir modifier les fichiers du répertoire /etc de l’hôte depuis le conteneur.
Testons donc :

# Create a container and mount host1's /etc to container's /root/etc
$ docker run --rm -v /etc:/root/etc -it busybox

# Check the owner of files in /root/etc, which should be "nobody nogroup".
root@d5802c5e670a:/# ls -la /root/etc/hosts
-rw-r--r-- 1 nobody nogroup 1153 Apr 16 10:28 /root/etc/hosts

# Try creating a file in /root/etc
root@d5802c5e670a:/# touch /root/etc/test
touch: cannot touch '/root/etc/test': Permission denied

# Try deleting a file
root@d5802c5e670a:/# rm /root/etc/hostname
rm: cannot remove '/root/etc/hostname': Permission denied

Cool, c’est exactement ce que cela devait faire ! Mais pourquoi ?

Un peu plus loin avec les User NameSpaces

Configuration de Docker

Lorsque vous configurez Docker pour utiliser la fonctionnalité userns-remap, vous devez spécifier un utilisateur et/ou un groupe valide du système:

$id -u USER
1000
$ getent group USER
USER:x:1000:

Le démon docker sera démarré avec l’option –userns-remap USER.

Remarques

– Si vous spécifiez « default » à la place de votre ‘USER’, un utilisateur et un groupe « dockremap » sont créés et utilisés à cette fin.
– N’oubliez pas de redémarrer le démon après avoir édité le fichier.

Configuration Système

Afin de mettre en place la translation d’UID/GID, vous devez modifier les deux fichiers: /etc/subuid et /etc/subgid.
Chaque fichier fonctionne de la même manière, mais l’un concerne la plage d’ID utilisateur et l’autre la plage d’ID de groupe.

Considérons l’entrée suivante dans /etc/subuid:

testuser:231072:65536

L’utilisateur root (UID 0) du conteneur va être mappé sur le système avec l´UID 231072, un fichier avec l’UID 33 dans le conteneur aura l’UID 231105 sur l’hôte. (231072 + 33 = 231105), la translation étant activée ici pour une plage consécutive de 65536 IDs.

Notes:

– Vous aurez compris que « testuser », correspond à l’utilisateur qui doit être référencé par userns-remap dans le fichier /etc/docker/daemon.json

"userns-remap" : "ẗestuser"

Il est possible d’affecter plusieurs plages à un utilisateur ou à un groupe en ajoutant plusieurs mapping sans chevauchement.

Mais, Docker utilisera uniquement les cinq premiers mapping, conformément à la limitation du noyau de seulement cinq entrées dans /proc/self/UID_map et /proc/self/GID_map.

testuser:200000:20
testuser:350000:65500

UID Docker 0 <=>; UID 200000 sur l’hôte
UID Docker 19 <=> UID 200019 sur l’hôte
UID Docker 20 <=> UID 350000 sur l’hôte

Essayons

$ cat /etc/subuid
seb:1000:1
seb:100000:65535

$ cat /etc/subgid
seb:982:1
seb:100000:65535

#Run a container
$ docker run --rm -it -v "$(pwd)/test:/test/" -it busybox

root@02a5bcc1757c:/# cd /test
root@02a5bcc1757c:/test# touch dockerrootfile.
root@02a5bcc1757c:/test# ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:25 dockerrootfile

# as file belongs to root : UID=GID=0
root@02a5bcc1757c:/test# ls -ln
total 0
-rw-r--r--. 1 0 0 0 Jun 11 16:25 dockerrootfile

#Check it on the host
$ ls -ln
total 0
-rw-r--r--. 1 1000 982 0 Jun 11 18:25 dockerrootfile

Le fichier d’UID 0 et GID 0 dans docker se voit affecter l’UID 1000 et GID 982 sur l’hôte, grâce au fichier /etc/subuid et /etc/subgid, vous vous souvenez ?

Et comme sur notre hôte UID de seb = 1000, GID de docker = 982, on trouve l’explication :

$ ls -l
total 0
-rw-r--r--. 1 seb docker 0 Jun 11 18:25 dockerrootfile

Maintenant essayons avec l’utilisateur : www-data (UID 33) dans le conteneur

root@02a5bcc1757c:# chmod 777 /test
root@02a5bcc1757c:# su -s /bin/sh www-data
www-data@02a5bcc1757c:# touch www-data-file
www-data@02a5bcc1757c:/test$ ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:36 rootfile
-rw-r--r--. 1 www-data www-data 0 Jun 11 16:38 www-data-file

www-data@02a5bcc1757c:/test$ ls -ln
total 0
-rw-r--r--. 1 0 0 0 Jun 11 16:36 dockerrootfile
-rw-r--r--. 1 33 33 0 Jun 11 16:38 www-data-file

# on host:
$ ls -l
total 0
-rw-r--r--. 1 seb docker 0 Jun 11 18:36 dockerrootfile
-rw-r--r--. 1 100032 100032 0 Jun 11 18:38 www-data-file

$ ls -ln
total 0
-rw-r--r--. 1 1000 982 0 Jun 11 18:36 dockerrootfile
-rw-r--r--. 1 100032 100032 0 Jun 11 18:38 www-data-file

Une fois de plus l’UID et le GID ont été modifiés en accord avec les paramètres des fichiers /etc/subuid et /etc/subgid.

Créons un fichier sur la machine hôte :

# on host
$ touch www-data-file-from-host
# in container :
www-data@02a5bcc1757c:/test$ ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:36 rootfile
-rw-r--r--. 1 www-data www-data 0 Jun 11 16:38 www-data-file
-rw-r--r--. 1 root nogroup 0 Jun 11 16:41 www-data-file-from-host

Dans le conteneur, il appartient a root:nogroup comme prévu.
Car si sur l’hôte il appartient à seb:seb, dans le conteneur :
– l’UID 1000 de seb devient 0 : root
– le GID 1000 de seb devient 65534 : nogroup car non inclus dans la translation d’ID.

This is the end 

Voilà ! En espérant que vous y verrez plus clair concernant la translation d’ID et la sécurisation des conteneurs Docker.
Pour rappel, un conteneur ne doit pas être lancé avec l’utilisateur root par défaut, mais un utilisateur propre au service déployé.

Pour compléter cet article, nous vous conseillons la lecture de l’ouvrage « Container Security » de Liz Rice : https://info.aquasec.com/container-security-book

Si vous utilisez un orchestrateur comme Kubernetes, l’utilisation de userns-remap n’est pas ‘encore’ mise en oeuvre, mais une proposition allant dans ce sens est en cours d’étude :
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/node-usernamespace-remapping.md

En attendant, d’autres mécanismes de sécurité ont été mis en oeuvre au travers des PodSecurityPolicies et d’OPA (Open Policy Agent) :

https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces
https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/

Merci pour votre lecture !