Exécuter un conteneur avec moins de privilèges

DockerBeginner
Pratiquer maintenant

Introduction

Les conteneurs empaquettent et exécutent des applications, mais cet empaquetage seul ne choisit pas une identité de processus sûre. Ce worker DeskLite préparé génère un petit rapport de tickets et s'exécute actuellement en tant que root.

Vous découvrirez le minimum nécessaire de Docker, puis reconstruirez et exécuterez le worker avec une identité non root et uniquement les accès aux fichiers nécessaires à sa tâche. Docker et une image de base fixe sont préparés ; aucun compte de registre ni expérience préalable de Docker n'est requis.

Découvrir le projet de conteneur préparé

Dans cette étape, vous identifierez les fichiers qui composent la tâche de génération de rapport.

Une image est un système de fichiers empaqueté accompagné d'une configuration de démarrage. Un conteneur est une instance d'image en cours d'exécution, avec ses propres processus et sa couche de système de fichiers accessible en écriture. Un Dockerfile décrit comment construire une image. Un tag, tel que ticket-report:practice, est un nom pratique pour une image ; il peut être réaffecté à une nouvelle image après une reconstruction.

cd /home/labex/project/ticket-worker
ls

Les éléments attendus sont Dockerfile, worker.sh, input et output. Le worker préparé lit des lignes CSV, écrit leur nombre dans un rapport, puis reste en cours d'exécution pour que nous puissions l'inspecter.

cat input/tickets.csv

Il y a trois lignes de tickets après l'en-tête. Le rapport attendu est tickets=3.

cat Dockerfile

FROM sélectionne l'image de base en cache, WORKDIR définit le répertoire de travail de l'image et COPY ajoute le worker. USER root choisit l'identité du processus. CMD est la commande de démarrage par défaut, représentée sous forme de liste. Vous ne modifierez que USER.

Le tag de base desklite-base:1.37.0 est déjà en cache à partir d'une image BusyBox fixe. Toutes les constructions et exécutions suivantes utilisent des fichiers locaux. Les commandes Docker utilisent sudo pour accéder au démon de ce lab. Cet accès au niveau de l'hôte est puissant ; il est distinct de l'identité applicative dont nous réduisons les privilèges à l'intérieur du conteneur.

Construire et exécuter le worker existant

Dans cette étape, vous établirez l'état initial fonctionnel mais trop privilégié.

docker build lit le Dockerfile et un contexte de construction, le répertoire des fichiers disponibles pour COPY. Ici, . désigne le répertoire actuel. -t attribue le tag d'image local :

sudo docker build -t ticket-report:practice .

La construction doit réussir. L'image existe, mais aucun worker n'est encore en cours d'exécution.

docker run crée un conteneur. -d l'exécute en arrière-plan et --name lui donne un nom réutilisable. --network none désactive le réseau, car ce rapport n'a besoin que de fichiers.

Un montage bind expose un chemin de l'hôte à un chemin du conteneur. Chaque --mount décrit son type, sa source (src) et sa destination (dst). Cet état initial expose les deux répertoires en écriture, car aucune option readonly n'est présente. Nous restreindrons l'entrée plus tard.

sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice

Docker affiche un identifiant de conteneur. Ce n'est pas l'identifiant utilisateur du worker. Le worker d'origine doit générer son rapport, que nous inspecterons ensuite.

Inspecter l'identité réelle à l'exécution

Dans cette étape, vous distinguerez un rapport réussi d'une exécution avec le moindre privilège.

docker ps liste les conteneurs en cours d'exécution :

sudo docker ps

Repérez ticket-worker. docker logs lit la sortie standard de l'application :

sudo docker logs ticket-worker

Les messages attendus sont worker uid=0 et tickets=3. L'UID 0 correspond à root. La tâche métier fonctionne, mais utilise davantage de privilèges que nécessaire.

docker exec exécute une commande d'inspection dans ce conteneur en cours d'exécution. id -u affiche son identifiant utilisateur numérique effectif :

sudo docker exec ticket-worker id -u

Le résultat attendu est encore 0. docker inspect lit la configuration. --format sélectionne un champ ; copiez exactement le modèle entre guillemets. .Config.User est l'utilisateur configuré :

sudo docker inspect --format '{{.Config.User}}' ticket-worker

Le résultat attendu est root. Le paramètre de l'image et l'inspection du processus réel concordent. Un fichier indiquant « non root » ne fournirait pas cette preuve.

Reconstruire avec l'utilisateur non root préparé

Dans cette étape, vous définirez l'identité applicative par défaut de l'image.

La base en cache contient déjà nobody, d'UID 65534. Aucune création d'utilisateur ni aucun mot de passe ne sont nécessaires. Ouvrez le Dockerfile :

nano Dockerfile

Modifiez uniquement l'instruction USER. Le fichier complet doit être :

FROM desklite-base:1.37.0
WORKDIR /app
COPY worker.sh /app/worker.sh
USER nobody
CMD ["sh", "/app/worker.sh"]

Enregistrez avec Ctrl+O, Enter, puis quittez avec Ctrl+X.

sudo docker build -t ticket-report:practice .

Le tag identifie maintenant l'image reconstruite. Les conteneurs existants ne basculent pas automatiquement vers une image reconstruite ; l'ancien conteneur root reste l'ancienne instance. À l'étape suivante, vous le remplacerez et accorderez à la nouvelle identité l'accès en sortie nécessaire.

Remplacer le conteneur avec des montages restreints

Dans cette étape, vous combinerez l'identité non root avec des permissions de fichiers fonctionnelles.

docker rm supprime un conteneur. -f l'arrête d'abord s'il est en cours d'exécution. Supprimez uniquement le conteneur nommé de cet exercice :

sudo docker rm -f ticket-worker

Le répertoire de rapport appartenait initialement à root. Comme dans le lab sur les permissions de fichiers, un processus non root a besoin de la propriété et des permissions nécessaires pour écrire sa sortie. chown -R attribue récursivement le répertoire et son rapport existant aux UID et GID 65534. Le mode 750 donne un accès complet au propriétaire sans accorder l'écriture à tous :

sudo chown -R 65534:65534 output
sudo chmod 750 output

Créez maintenant le remplaçant. Le montage d'entrée ajoute readonly ; le montage de sortie reste accessible en écriture. Cela empêche la modification de l'entrée tout en permettant de générer le rapport :

sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input,readonly --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice

Un nouvel identifiant de conteneur est attendu. L'image fournit maintenant l'utilisateur non root ; aucune substitution par root à l'exécution n'est nécessaire.

Vérifier l'identité, les accès et la sortie métier

Dans cette étape, vous vérifierez les trois parties du résultat sur le conteneur de remplacement.

sudo docker exec ticket-worker id -u

Le résultat attendu est 65534. Vérifiez la sortie réelle du worker :

sudo docker logs ticket-worker

Les messages attendus sont worker uid=65534 et tickets=3. Lisez le véritable fichier de sortie à travers le conteneur :

sudo docker exec ticket-worker cat /data/output/report.txt

Le résultat attendu est tickets=3. Cela prouve que la réduction des privilèges n'a pas cassé la tâche.

Inspectez les montages. Le modèle entre guillemets sélectionne .Mounts et le met en forme en JSON :

sudo docker inspect --format '{{json .Mounts}}' ticket-worker

Repérez /data/input avec "RW":false et /data/output avec "RW":true. RW signifie lecture/écriture. Les mêmes octets d'entrée sont disponibles en lecture, mais le montage interdit l'écriture.

Essayez de créer un fichier sans conséquence pour tester cette restriction. touch créerait un fichier vide si cela était autorisé :

sudo docker exec ticket-worker touch /data/input/probe.txt

Une erreur Read-only file system et un code de sortie non nul sont attendus. Ce refus est intentionnel. Ne rendez pas l'entrée accessible en écriture pour le supprimer ; le rapport a déjà été produit avec succès à partir d'une entrée en lecture seule.

Confirmer que le worker final fonctionne toujours

Dans cette étape, vous laisserez le worker prévu prêt à être inspecté.

sudo docker ps

Le conteneur nommé ticket-worker doit être en cours d'exécution. Confirmez à nouveau l'utilisateur configuré et le rapport :

sudo docker inspect --format '{{.Config.User}}' ticket-worker
sudo docker exec ticket-worker cat /data/output/report.txt

Les résultats attendus sont nobody et tickets=3. La vérification du cours compare aussi l'image en cours d'exécution à votre tag de livraison, vérifie le PID réel du worker et teste l'entrée et la sortie d'origine. Reconstruire un Dockerfile sans remplacer le conteneur ne suffit pas.

Cet exercice réduit les privilèges de l'identité applicative et l'accès aux fichiers. Il ne rend pas l'accès au démon Docker non privilégié et n'établit pas une politique complète de durcissement des conteneurs en production.

Résumé

Vous avez construit une véritable image avec une identité applicative non root, exécuté cette image avec une entrée restreinte et un accès en sortie fonctionnel, puis vérifié le processus et le rapport réels. La configuration de l'image, l'identité à l'exécution, les modes de montage et le comportement fonctionnel contribuent tous à l'état final.