Quelle est la meilleure façon de gérer les permissions pour les volumes partagés Docker?

Je joue avec Docker depuis un certain temps et continue à trouver le même problème lorsque je traite des données persistantes.

Je crée mon Dockerfile et Dockerfile un volume ou utilise --volumes-from pour monter un dossier hôte dans mon conteneur .

Quelles permissions dois-je appliquer au volume partagé sur l’hôte?

Je peux penser à deux options:

  • Jusqu’à présent, j’ai donné à tout le monde access en lecture / écriture, donc je peux écrire dans le dossier depuis le conteneur Docker.

  • Mappez les utilisateurs de l’hôte dans le conteneur afin de pouvoir atsortingbuer des permissions plus granulaires. Je ne suis pas sûr que cela soit possible et que je n’en ai pas beaucoup trouvé. Jusqu’à présent, tout ce que je peux faire est d’exécuter le conteneur en tant qu’utilisateur: docker run -i -t -user="myuser" postgres , mais cet utilisateur a un UID différent de celui de mon hôte myuser , donc les permissions ne fonctionnent pas. De plus, je ne suis pas certain que le mappage des utilisateurs entraînera des risques de sécurité.

Y a-t-il d’autres alternatives?

Comment allez-vous les gars / filles face à cette question?

UPDATE 2016-03-02 : Depuis Docker 1.9.0, Docker a nommé des volumes qui remplacent les conteneurs de données uniquement . La réponse ci-dessous, ainsi que mon article de blog lié, ont encore de la valeur en termes de reflection sur les données dans docker, mais envisagez d’utiliser des volumes nommés pour implémenter le modèle décrit ci-dessous plutôt que des conteneurs de données.


Je crois que la manière canonique de résoudre ce problème est d’utiliser des conteneurs de données uniquement . Avec cette approche, tout access aux données de volume se fait via des conteneurs qui utilisent -volumes-from le conteneur de données, de sorte que l’ -volumes-from utilisateur / gid de l’hôte n’a pas d’importance.

Par exemple, un cas d’utilisation donné dans la documentation est la sauvegarde d’un volume de données. Pour ce faire, un autre conteneur est utilisé pour effectuer la sauvegarde via tar et il utilise également -volumes-from pour monter le volume. Donc, je pense que le point clé de grok est: plutôt que de réfléchir à la manière d’accéder aux données de l’hôte avec les permissions appropriées, réfléchissez à la manière dont vous avez besoin – sauvegardes, navigation, etc. – via un autre conteneur. . Les conteneurs eux-mêmes doivent utiliser des uid / gids cohérents, mais ils n’ont pas besoin d’être mappés sur l’hôte, ce qui leur permet de restr portables.

C’est relativement nouveau pour moi aussi, mais si vous avez un cas d’utilisation particulier, n’hésitez pas à commenter et je vais essayer de développer la réponse.

UPDATE : Pour le cas d’utilisation donné dans les commentaires, vous pouvez avoir une image some/graphite pour exécuter graphite et une image some/graphitedata comme conteneur de données. Donc, en ignorant les ports et autres, le Dockerfile de image some/graphitedata est quelque chose comme:

 FROM debian:jessie # add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later RUN groupadd -r graphite \ && useradd -r -g graphite graphite RUN mkdir -p /data/graphite \ && chown -R graphite:graphite /data/graphite VOLUME /data/graphite USER graphite CMD ["echo", "Data container for graphite"] 

Construisez et créez le conteneur de données:

 docker build -t some/graphitedata Dockerfile docker run --name graphitedata some/graphitedata 

Le fichier Dockerfile some/graphite devrait également avoir les mêmes uid / gids, par conséquent il pourrait ressembler à ceci:

 FROM debian:jessie # add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later RUN groupadd -r graphite \ && useradd -r -g graphite graphite # ... graphite installation ... VOLUME /data/graphite USER graphite CMD ["/bin/graphite"] 

Et ce serait comme suit:

 docker run --volumes-from=graphitedata some/graphite 

Ok, maintenant cela nous donne notre conteneur de graphite et le conteneur associé aux données avec le bon utilisateur / groupe (notez que vous pourriez également réutiliser le conteneur some/graphite pour le conteneur de données, en écrasant le entrypoing / cmd lors de son exécution, mais les avoir comme images séparées IMO est plus clair).

Maintenant, disons que vous voulez éditer quelque chose dans le dossier de données. Donc, plutôt que de lier le volume à l’hôte et de le modifier, créez un nouveau conteneur pour effectuer ce travail. Permet de l’appeler some/graphitetools . Permet également de créer l’utilisateur / le groupe approprié, tout comme l’image some/graphite .

 FROM debian:jessie # add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later RUN groupadd -r graphite \ && useradd -r -g graphite graphite VOLUME /data/graphite USER graphite CMD ["/bin/bash"] 

Vous pouvez faire ce DRY en héritant de some/graphite ou de some/graphitedata dans le fichier Dockerfile ou, au lieu de créer une nouvelle image, réutilisez simplement l’un des fichiers existants (en remplaçant le point d’entrée / cmd si nécessaire).

Maintenant, vous exécutez simplement:

 docker run -ti --rm --volumes-from=graphitedata some/graphitetools 

et ensuite vi /data/graphite/whatever.txt . Cela fonctionne parfaitement car tous les conteneurs ont le même utilisateur graphite avec uid / gid correspondant.

Comme vous ne montez jamais /data/graphite partir de l’hôte, vous ne vous souciez pas de la façon dont l’ID utilisateur / gid de l’hôte correspond à l’ID / gid défini dans les conteneurs graphite et graphitetools . Ces conteneurs peuvent maintenant être déployés sur n’importe quel hôte et continueront de fonctionner parfaitement.

La chose intéressante à ce sujet est que graphitetools pourrait avoir toutes sortes d’utilitaires et de scripts utiles, que vous pouvez maintenant également déployer de manière portable.

MISE À JOUR 2 : Après avoir écrit cette réponse, j’ai décidé d’écrire un article de blog plus complet sur cette approche. J’espère que ça aide.

MISE À JOUR 3 : J’ai corrigé cette réponse et ajouté plus de détails. Il contenait auparavant des hypothèses incorrectes concernant la propriété et les perms – la propriété est généralement atsortingbuée au moment de la création du volume, c’est-à-dire dans le conteneur de données, car c’est à ce moment-là que le volume est créé. Voir ce blog . Ce n’est pas une exigence – vous pouvez simplement utiliser le conteneur de données en tant que “référence / handle” et définir la propriété / permanente dans un autre conteneur via chown dans un point d’entrée, qui se termine par gosu pour exécuter la commande en tant qu’utilisateur correct. Si quelqu’un est intéressé par cette approche, veuillez commenter et je peux fournir des liens vers un échantillon en utilisant cette approche.

Une solution très élégante peut être vue sur l’ image officielle redis et en général sur toutes les images officielles.

Décrit dans un processus pas à pas:

  • Créer un utilisateur / groupe redis avant toute autre chose

Comme vu sur les commentaires de Dockerfile:

ajoutez d’abord notre utilisateur et notre groupe pour nous assurer que leurs identifiants sont atsortingbués de manière cohérente, quelles que soient les dépendances ajoutées

  • Installez gosu avec Dockerfile

gosu est une alternative à su / sudo pour faciliter la suppression de l’utilisateur root. (Redis est toujours exécuté avec l’utilisateur redis )

  • Configurez /data volume de /data et définissez-le comme workdir

En configurant le volume / data avec la commande VOLUME /data nous avons maintenant un volume distinct qui peut être soit un volume Docker, soit un répertoire lié à un hôte.

Le configurer en tant que répertoire de travail ( WORKDIR /data ) en fait le répertoire par défaut où les commandes sont exécutées.

  • Ajouter le fichier docker-entrypoint et le définir comme ENTRYPOINT avec le redis-server CMD par défaut

Cela signifie que toutes les exécutions de conteneur s’exécuteront via le script docker-entrypoint et que, par défaut, la commande à exécuter est redis-server.

docker-entrypoint est un script qui docker-entrypoint une fonction simple: modifiez la propriété du répertoire actuel (/ data) et descendez de l’utilisateur root à l’utilisateur redis pour exécuter redis-server . (Si la commande exécutée n’est pas redis-server, elle exécutera directement la commande.)

Cela a l’effet suivant

Si le répertoire / data est lié à l’hôte, le point d’entrée docker préparera les permissions utilisateur avant d’exécuter redis-server sous redis utilisateur redis .

Cela vous donne la certitude qu’il n’y a aucune configuration pour exécuter le conteneur sous n’importe quelle configuration de volume.

Bien sûr, si vous avez besoin de partager le volume entre différentes images, vous devez vous assurer qu’ils utilisent le même ID utilisateur / groupid, sinon le dernier conteneur détournera les droits utilisateur du précédent.

Ce n’est sans doute pas le meilleur moyen pour la plupart des circonstances, mais cela n’a pas encore été mentionné, alors peut-être que cela aidera quelqu’un.

  1. Volume d’hôte de assembly de liaison

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  2. Modifier le script de démarrage de votre conteneur pour trouver le GID du volume qui vous intéresse

    $ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)

  3. Assurez-vous que votre utilisateur appartient à un groupe avec ce GID (vous devrez peut-être créer un nouveau groupe). Pour cet exemple, je vais prétendre que mon logiciel s’exécute en tant qu’utilisateur sans nobody à l’intérieur du conteneur, donc je veux m’assurer que nobody n’appartient à un groupe avec un identifiant de groupe égal à TARGET_GID

  EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l) # Create new group using target GID and add nobody user if [ $EXISTS == "0" ]; then groupadd -g $TARGET_GID tempgroup usermod -a -G tempgroup nobody else # GID exists, find group name and add GROUP=$(getent group $TARGET_GID | cut -d: -f1) usermod -a -G $GROUP nobody fi 

J’aime cela parce que je peux facilement modifier les permissions de groupe sur mes volumes d’hôte et savoir que ces permissions mises à jour s’appliquent à l’intérieur du conteneur docker. Cela se produit sans aucune permission ou modification de propriété de mes dossiers / fichiers hôtes, ce qui me rend heureux.

Je n’aime pas cela car cela suppose qu’il n’y a aucun danger à vous append à des groupes arbitraires à l’intérieur du conteneur qui utilisent un GID que vous voulez. Il ne peut pas être utilisé avec une clause USER dans un fichier Docker (sauf si l’utilisateur dispose des privilèges root, je suppose). En outre, il crie du travail de piratage 😉

Si vous voulez être hardcore, vous pouvez évidemment étendre cela de plusieurs manières – par exemple, rechercher tous les groupes sur des sous-fichiers, plusieurs volumes, etc.

Ok, cela est maintenant suivi à la question n ° 7198

Pour l’instant, je traite de ceci en utilisant votre deuxième option:

Mapper les utilisateurs de l’hôte dans le conteneur

Dockerfile

 #======= # Users #======= # TODO: Idk how to fix hardcoding uid & gid, specifics to docker host machine RUN (adduser --system --uid=1000 --gid=1000 \ --home /home/myguestuser --shell /bin/bash myguestuser) 

CLI

 # DIR_HOST and DIR_GUEST belongs to uid:gid 1000:1000 docker run -d -v ${DIR_HOST}:${DIR_GUEST} elgalu/myservice:latest 

MISE À JOUR Je suis actuellement plus enclin à répondre à Hamy

Essayez d’append une commande à Dockerfile

 RUN usermod -u 1000 www-data 

les crédits vont à https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272

Voici une approche qui utilise toujours un conteneur de données uniquement, mais ne nécessite pas sa synchronisation avec le conteneur d’application (en termes de même identifiant / identifiant).

Vraisemblablement, vous voulez exécuter une application dans le conteneur en tant que non utilisateur root sans un shell de connexion.

Dans le fichier Docker:

 RUN useradd -s /bin/false myuser # Set environment variables ENV VOLUME_ROOT /data ENV USER myuser ... ENTRYPOINT ["./entrypoint.sh"] 

Ensuite, dans entrypoint.sh:

 chown -R $USER:$USER $VOLUME_ROOT su -s /bin/bash - $USER -c "cd $repo/build; $@" 

Pour sécuriser et modifier la racine du conteneur docker, un hôte docker doit utiliser les --uidmap et --private-uids

https://github.com/docker/docker/pull/4572#issuecomment-38400893

Vous pouvez également supprimer plusieurs fonctionnalités ( --cap-drop ) dans le conteneur --cap-drop de sécurité.

http://opensource.com/business/14/9/security-for-docker

La prise en charge UPDATE devrait être disponible dans le docker > 1.7.0

UPDATE Version 1.10.0 (2016-02-04) append --userns-remap flag https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2

Comme vous, je cherchais un moyen de mapper les utilisateurs / groupes d’hôte vers des conteneurs Docker, et c’est le moyen le plus court que j’ai trouvé jusqu’à présent:

  version: "3" services: my-service: ..... volumes: # take uid/gid lists from host - /etc/passwd:/etc/passwd:ro - /etc/group:/etc/group:ro # mount config folder - path-to-my-configs/my-service:/etc/my-service:ro ..... 

Ceci est un extrait de mon docker-compose.yml.

L’idée est de monter (en mode lecture seule) des listes d’utilisateurs / groupes depuis l’hôte vers le conteneur. Une fois que le conteneur aura démarré, il aura les mêmes correspondances uid-> username (ainsi que pour les groupes) avec l’hôte. Vous pouvez maintenant configurer les parameters utilisateur / groupe de votre service dans le conteneur comme s’il fonctionnait sur votre système hôte.

Lorsque vous décidez de déplacer votre conteneur vers un autre hôte, il vous suffit de changer le nom d’utilisateur dans le fichier de configuration du service en celui que vous avez sur cet hôte.

Image de base

Utilisez cette image: https://hub.docker.com/r/reduardo7/docker-host-user

ou

Important: cela détruit la portabilité du conteneur entre les hôtes .

1) init.sh

 #!/bin/bash if ! getent passwd $DOCKDEV_USER_NAME > /dev/null then echo "Creating user $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME" groupadd --gid $DOCKDEV_GROUP_ID -r $DOCKDEV_GROUP_NAME useradd --system --uid=$DOCKDEV_USER_ID --gid=$DOCKDEV_GROUP_ID \ --home-dir /home --password $DOCKDEV_USER_NAME $DOCKDEV_USER_NAME usermod -a -G sudo $DOCKDEV_USER_NAME chown -R $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME /home fi sudo -u $DOCKDEV_USER_NAME bash 

2) Dockerfile

 FROM ubuntu:latest # Volumes VOLUME ["/home/data"] # Copy Files COPY /home/data/init.sh /home # Init RUN chmod a+x /home/init.sh 

3) run.sh

 #!/bin/bash DOCKDEV_VARIABLES=(\ DOCKDEV_USER_NAME=$USERNAME\ DOCKDEV_USER_ID=$UID\ DOCKDEV_GROUP_NAME=$(id -g -n $USERNAME)\ DOCKDEV_GROUP_ID=$(id -g $USERNAME)\ ) cmd="docker run" if [ ! -z "${DOCKDEV_VARIABLES}" ]; then for v in ${DOCKDEV_VARIABLES[@]}; do cmd="${cmd} -e ${v}" done fi # /home/usr/data contains init.sh $cmd -v /home/usr/data:/home/data -i -t my-image /home/init.sh 

4) Construire avec docker

4) courir!

 sh run.sh 

Pour partager un dossier entre l’hôte docker et le conteneur docker, essayez la commande ci-dessous

$ docker run -v “$ (pwd): $ (pwd)” -i -t ubuntu

L’indicateur -v monte le répertoire de travail actuel dans le conteneur. Lorsque le répertoire hôte d’un volume monté sur bind n’existe pas, Docker créera automatiquement ce répertoire sur l’hôte,

Cependant, il y a 2 problèmes que nous avons ici:

  1. Vous ne pouvez pas écrire sur le volume monté si vous étiez un utilisateur non root car le fichier partagé appartiendra à un autre utilisateur de l’hôte,
  2. Vous ne devez pas exécuter le processus dans vos conteneurs en tant que root, mais même si vous utilisez un utilisateur codé en dur, il ne correspond toujours pas à l’utilisateur de votre ordinateur portable / Jenkins,

Solution:

Container: créer un utilisateur disons ‘testuser’, par défaut, l’ID utilisateur commencera à 1000,

Hôte: créez un groupe dites “groupe de test” avec l’ID de groupe 1000 et indiquez le répertoire dans le nouveau groupe (groupe de test

Mon approche consiste à détecter l’UID / GID actuel, puis à créer un tel utilisateur / groupe dans le conteneur et à exécuter le script sous lui. Tous les fichiers qu’il créera correspondront à l’utilisateur qui exécutera le script:

 # get location of this script no matter what your current folder is, this might break between shells so make sure you run bash LOCAL_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # get current IDs USER_ID=$(id -u) GROUP_ID=$(id -g) echo "Mount $LOCAL_DIR into docker, and match the host IDs ($USER_ID:$GROUP_ID) inside the container." docker run -v $LOCAL_DIR:/host_mount -i debian:9.4-slim bash -c "set -euo pipefail && groupadd -r -g $GROUP_ID lowprivgroup && useradd -u $USER_ID lowprivuser -g $GROUP_ID && cd /host_mount && su -c ./runMyScriptAsRegularUser.sh lowprivuser" 

Dans mon cas spécifique, j’essayais de construire mon paquetage de nœud avec l’image de nœud de station afin de ne pas avoir à installer npm sur le serveur de déploiement. Cela a bien fonctionné jusqu’à ce que, en dehors du conteneur et sur la machine hôte, j’essaie de déplacer un fichier dans le répertoire node_modules créé par l’image du docker du nœud, auquel je me suis vu refuser l’access. J’ai réalisé que je pouvais contourner ce problème en copiant le répertoire hors du conteneur sur la machine hôte. Via docker docs …

Les fichiers copiés sur la machine locale sont créés avec l’UID: GID de l’utilisateur qui a appelé la commande docker cp.

C’est le code bash que j’ai utilisé pour changer la propriété du répertoire créé par et dans le conteneur docker.

 NODE_IMAGE=node_builder docker run -v $(pwd)/build:/build -w="/build" --name $NODE_IMAGE node:6-slim npm i --production # node_modules is owned by root, so we need to copy it out docker cp $NODE_IMAGE:/build/node_modules build/lambda # you might have issues trying to remove the directory "node_modules" within the shared volume "build", because it is owned by root, so remove the image and its volumes docker rm -vf $NODE_IMAGE || true 

Si nécessaire, vous pouvez supprimer le répertoire avec un deuxième conteneur Docker.

 docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules 

Si vous utilisez Docker Compose, démarrez le conteneur en mode prédiligné:

 wordpress: image: wordpress:4.5.3 restart: always ports: - 8084:80 privileged: true