Qual é a (melhor) maneira de gerenciar permissões para volumes compartilhados do Docker?

345

Venho brincando com o Docker há um tempo e continuo encontrando o mesmo problema ao lidar com dados persistentes.

Crio Dockerfilee exponho um volume ou uso --volumes-frompara montar uma pasta host dentro do meu contêiner .

Quais permissões devo aplicar ao volume compartilhado no host?

Eu posso pensar em duas opções:

  • Até agora, eu dei a todos acesso de leitura / gravação, para que eu possa gravar na pasta do contêiner do Docker.

  • Mapeie os usuários do host para o contêiner, para que eu possa atribuir permissões mais granulares. Não tenho certeza se isso é possível e não encontrei muito sobre isso. Até agora, tudo o que posso fazer é executar o contêiner como um usuário:, docker run -i -t -user="myuser" postgresmas esse usuário tem um UID diferente do meu host myuser, portanto, as permissões não funcionam. Além disso, não tenho certeza se o mapeamento dos usuários representará alguns riscos à segurança.

Existem outras alternativas?

Como vocês estão lidando com esse problema?

Xabs
fonte
Faça o checkout desta resposta de uma pergunta semelhante .
Batandwa
11
Você também pode estar interessado neste tópico que discute este tema com algum detalhe: groups.google.com/forum/#!msg/docker-user/cVov44ZFg_c/...
btiernay
No momento, a equipe do Docker não planeja implementar uma solução nativa para montar diretórios de host como um volume com uid / gid especificado. Veja meu comentário e respostas sobre esta questão: github.com/docker/docker/issues/7198#issuecomment-230636074
Quinn Comendant

Respostas:

168

ATUALIZAÇÃO 02-03-2016 : A partir do Docker 1.9.0, o Docker nomeou volumes que substituem os contêineres somente de dados . A resposta abaixo, assim como minha postagem no blog vinculada, ainda tem valor no sentido de pensar sobre os dados dentro da janela de encaixe, mas considere usar volumes nomeados para implementar o padrão descrito abaixo, em vez de contêineres.


Acredito que a maneira canônica de resolver isso é usando contêineres somente de dados . Com essa abordagem, todo o acesso aos dados do volume é feito por contêineres que usam-volumes-from o contêiner de dados, portanto, o host uid / gid não importa.

Por exemplo, um caso de uso fornecido na documentação está fazendo backup de um volume de dados. Para fazer isso, outro contêiner é usado para fazer o backup viatar e também usa-volumes-from para montar o volume. Então, acho que o ponto principal do grok é: em vez de pensar em como obter acesso aos dados no host com as permissões adequadas, pense em como fazer o que for necessário - backups, navegação etc. - através de outro contêiner . Os próprios contêineres precisam usar uid / gids consistentes, mas não precisam mapear para nada no host, permanecendo portáteis.

Isso também é relativamente novo para mim, mas se você tiver um caso de uso específico, sinta-se à vontade para comentar e tentarei expandir a resposta.

ATUALIZAÇÃO : Para o caso de uso especificado nos comentários, você pode ter uma imagem some/graphitepara executar grafite e uma imagem some/graphitedatacomo o contêiner de dados. Portanto, ignorando portas e outras coisas, a Dockerfileimagem some/graphitedataé algo como:

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"]

Crie e crie o contêiner de dados:

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

O some/graphiteDockerfile também deve receber o mesmo uid / gids, portanto, pode ser algo como isto:

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"]

E seria executado da seguinte maneira:

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

Ok, agora isso nos fornece nosso contêiner de grafite e um contêiner somente de dados associado ao usuário / grupo correto (observe que você pode reutilizar o some/graphite contêiner para o contêiner de dados, substituindo o entrypoing / cmd ao executá-lo, mas tendo-o como imagens separadas O IMO é mais claro).

Agora, digamos que você queira editar algo na pasta de dados. Portanto, em vez de vincular a montagem do volume ao host e editá-lo lá, crie um novo contêiner para fazer esse trabalho. Vamos chamá-lo some/graphitetools. Permite também criar o usuário / grupo apropriado, assim como a some/graphiteimagem.

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"]

Você pode tornar esse DRY herdando some/graphiteousome/graphitedata na Dockerfile, ou em vez de criar uma nova imagem apenas um dos existentes re-uso (substituindo entrypoint / cmd, se necessário).

Agora, você simplesmente executa:

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

e depois vi /data/graphite/whatever.txt . Isso funciona perfeitamente porque todos os contêineres têm o mesmo usuário de grafite com uid / gid correspondente.

Como você nunca monta a /data/graphitepartir do host, não se importa como o uid / gid do host é mapeado para o uid / gid definido dentro do graphiteegraphitetools contêineres . Esses contêineres agora podem ser implantados em qualquer host e continuarão funcionando perfeitamente.

O mais interessante disso é que graphitetoolspoderia ter todos os tipos de utilitários e scripts úteis, que agora você também pode implantar de maneira portátil.

ATUALIZAÇÃO 2 : Depois de escrever esta resposta, decidi escrever uma postagem de blog mais completa sobre essa abordagem. Espero que ajude.

ATUALIZAÇÃO 3 : Corrigi esta resposta e adicionei mais detalhes. Antes, ele continha algumas suposições incorretas sobre propriedade e permissões - a propriedade geralmente é atribuída no momento da criação do volume, ou seja, no contêiner de dados, porque é quando o volume é criado. Veja este blog . Porém, isso não é um requisito - você pode simplesmente usar o contêiner de dados como uma "referência / manipulador" e definir a propriedade / permissões em outro contêiner via chown em um ponto de entrada, que termina com gosu para executar o comando como o usuário correto. Se alguém estiver interessado nessa abordagem, comente e eu posso fornecer links para uma amostra usando essa abordagem.

Raman
fonte
35
Receio que isso não seja uma solução, pois você terá o mesmo problema com os contêineres somente de dados. No final do dia, esses contêineres usarão volumes compartilhados do host, portanto, você ainda precisará gerenciar permissões nessas pastas compartilhadas.
Xab #
2
Lembre-se de que talvez eu precise editar a pasta de dados do meu host (por exemplo: excluir uma chave de grafite de teste, excluir minha pasta inicial de teste do JIRA ou atualizá-la com o backup de produção mais recente ...). Pelo que entendi pelo seu comentário, eu deveria fazer coisas como atualizar os dados do JIRA por meio de um terceiro contêiner. De qualquer forma, quais permissões você aplicaria a uma nova pasta de dados /data/newcontainer? Suponho que você execute dockercomo root (é possível não fazer isso?) Além disso, há alguma diferença nessas permissões se os dados forem montados diretamente no contêiner principal ou por meio de um contêiner somente de dados ?
Xabs
2
Obrigado pela sua resposta elaborada. Vou testar isso assim que tiver a chance. Além disso, boas referências tanto à postagem do seu blog quanto à sobre o uso de imagens mínimas para contêineres de dados .
Xab # 25/14
3
O único problema com essa abordagem é muito fácil excluir um contêiner por engano. Imagine se esse for o seu contêiner de dados. Eu acho que (CMIIW) os dados ainda estará em /var/lib/dockeralgum lugar, mas ainda uma enorme dor
lolski
3
"você pode simplesmente usar o contêiner de dados como uma" referência / manipulação "e definir a propriedade / permissões em outro contêiner via chown em um ponto de entrada" ... @Raman: Esta é a seção que finalmente me salvou depois de ter vários problemas de permissão, descobrir. Usar um script de ponto de entrada e definir permissões neste funciona para mim. Obrigado pela sua explicação elaborada. É o melhor que encontrei na web até agora.
Vanderstaaij
59

Uma solução muito elegante pode ser vista na imagem oficial do redis e em geral em todas as imagens oficiais.

Descrito no processo passo a passo:

  • Crie usuário / grupo redis antes de qualquer outra coisa

Como visto nos comentários do Dockerfile:

adicione nosso usuário e grupo primeiro para garantir que seus IDs sejam atribuídos de forma consistente, independentemente de quaisquer dependências adicionadas

  • Instale o gosu com o Dockerfile

O gosu é uma alternativa de su/ sudopara facilitar o abandono do usuário root. (Redis é sempre executado com o redisusuário)

  • Configure o /datavolume e defina-o como workdir

Ao configurar o volume / data com o VOLUME /datacomando, agora temos um volume separado que pode ser volume do docker ou montado em ligação a um diretório de host.

A configuração como workdir ( WORKDIR /data) torna o diretório padrão de onde os comandos são executados.

  • Inclua o arquivo docker-entrypoint e defina-o como ENTRYPOINT com redis-server CMD padrão

Isso significa que todas as execuções de contêiner serão executadas pelo script docker-entrypoint e, por padrão, o comando a ser executado é redis-server.

docker-entrypointé um script que executa uma função simples: Altere a propriedade do diretório atual (/ data) e desça de rootpara o redisusuário para executar redis-server. (Se o comando executado não for redis-server, ele executará o comando diretamente.)

Isso tem o seguinte efeito

Se o diretório / data for montado em ligação ao host, o docker-entrypoint preparará as permissões do usuário antes de executar o redis-server no redisusuário.

Isso lhe dá a tranqüilidade de que não há configuração zero para executar o contêiner em qualquer configuração de volume.

Obviamente, se você precisar compartilhar o volume entre imagens diferentes, precisará garantir que eles usem o mesmo ID do usuário / ID do grupo, caso contrário, o contêiner mais recente seqüestrará as permissões de usuário do anterior.

Dimitris
fonte
11
A resposta aceita é informativa, mas só me levou a uma semana de frustrações com permissões para encontrar essa resposta que realmente fornece uma maneira canônica de resolver o problema.
M0meni 27/08/2015
3
Muito bem explicado também aqui: denibertovic.com/posts/handling-permissions-with-docker-volumes
kheraud
Assim? Como faço para gravar o volume de dentro da janela de encaixe? chowndentro do ENTRYPOINTscript?
Gherman
34

Este não é o melhor caminho para a maioria das circunstâncias, mas ainda não foi mencionado, portanto talvez ajude alguém.

  1. Volume do host de montagem de ligação

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  2. Modifique o script de inicialização do contêiner para encontrar o GID do volume em que você está interessado

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

  3. Verifique se o usuário pertence a um grupo com esse GID (talvez seja necessário criar um novo grupo). Neste exemplo, fingirei que meu software é executado como nobodyusuário quando está dentro do contêiner, portanto, quero garantir que nobodypertença a um grupo com um ID de grupo igual aTARGET_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

Gosto disso porque posso modificar facilmente as permissões de grupo nos volumes do meu host e sei que essas permissões atualizadas se aplicam dentro do contêiner do Docker. Isso acontece sem nenhuma permissão ou modificação de propriedade nas pastas / arquivos do host, o que me deixa feliz.

Eu não gosto disso porque supõe que não há perigo em se adicionar a grupos arbitrários dentro do contêiner que estejam usando o GID desejado. Ele não pode ser usado com uma USERcláusula em um Dockerfile (a menos que esse usuário tenha privilégios de root, suponho). Além disso, ele grita hack job ;-)

Se você quer ser hardcore, obviamente pode estender isso de várias maneiras - por exemplo, pesquise todos os grupos em qualquer subarquivo, vários volumes, etc.

Hamy
fonte
4
Isso tem como objetivo ler arquivos do volume montado? Estou procurando uma solução para gravar arquivos sem que eles sejam de propriedade de outro usuário que não seja o que criou o contêiner do docker.
precisa saber é o seguinte
Estou usando essa abordagem desde 15 de agosto. Tudo estava ok. Apenas as permissões dos arquivos criados dentro do contêiner eram distintas. Ambos, os usuários (dentro e fora do contêiner) possuem as propriedades de seus arquivos, mas os dois tiveram acesso de leitura porque pertenciam ao mesmo grupo criado por esta solução. O problema começou quando um caso de uso impôs acesso de gravação a arquivos comuns. O maior problema foi que os volumes compartilhados tinham arquivos git (é um volume para testar arquivos de origem dev no mesmo contexto de produção). O Git começou a avisar sobre o problema de acesso ao código compartilhado.
yucer
Eu acho que um grep melhor $TARGET_GIDseria usar grep ':$TARGET_GID:', caso contrário, se o contêiner tiver, por exemplo, gid 10001 e seu host for 1000, essa verificação será aprovada, mas não deve.
22418 Robhudson
16

Ok, isso está sendo rastreado na edição docker # 7198

Por enquanto, estou lidando com isso usando sua segunda opção:

Mapear os usuários do host para o contêiner

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

ATUALIZAÇÃO Atualmente, estou mais inclinado a responder ao Hamy

Leo Gallucci
fonte
11
usar o comando id -u <username>, id -g <username>, id -G <username>para obter o ID de usuário e ID de grupo de um usuário específico em vez
lolski
15
Isso destrói a portabilidade do contêiner entre hosts.
Raman
2
O problema do Docker nº 7198 chegou à conclusão de que eles não estarão implementando uma solução nativa para isso. Veja meu comentário e respostas em github.com/docker/docker/issues/7198#issuecomment-230636074
Quinn Comendant
12

Da mesma forma que você, eu estava procurando uma maneira de mapear usuários / grupos de contêineres de host para docker e esta é a maneira mais curta que encontrei até agora:

  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
        .....

Este é um extrato do meu docker-compose.yml.

A idéia é montar (no modo somente leitura) listas de usuários / grupos do host para o contêiner, assim, após a inicialização do contêiner, ele terá o mesmo uid-> nome de usuário (assim como para grupos) correspondências com o host. Agora você pode definir as configurações de usuário / grupo para o seu serviço dentro do contêiner como se estivesse funcionando no sistema host.

Quando você decide mover seu contêiner para outro host, basta alterar o nome de usuário no arquivo de configuração de serviço para o que você possui nesse host.

Alex Myznikov
fonte
Essa é uma ótima resposta, muito simples, se você deseja executar contêineres que manipulam arquivos em um sistema base, sem expor o restante do sistema.
Icarito 9/04
Esta é a minha resposta favorita. Além disso, vi uma recomendação semelhante em outro lugar com o comando docker run, no qual você passa seu nome de usuário / grupos atual -u $( id -u $USER ):$( id -g $USER )e não precisa mais se preocupar com o nome de usuário. Isso funciona bem para ambientes de desenvolvimento locais onde você deseja gerar arquivos (binários por exemplo) aos quais você tem acesso de leitura / gravação por padrão.
precisa saber é o seguinte
5

Aqui está uma abordagem que ainda usa um contêiner somente de dados, mas não exige que seja sincronizado com o contêiner do aplicativo (em termos de ter o mesmo uid / gid).

Presumivelmente, você deseja executar algum aplicativo no contêiner como $ USER não raiz sem um shell de logon.

No Dockerfile:

RUN useradd -s /bin/false myuser

# Set environment variables
ENV VOLUME_ROOT /data
ENV USER myuser

...

ENTRYPOINT ["./entrypoint.sh"]

Então, em entrypoint.sh:

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

Para proteger e alterar a raiz do contêiner de docker, um host de docker tenta usar --uidmape --private-uidsopções

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

Você também pode remover vários recursos ( --cap-drop) no contêiner do docker para segurança

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

O suporte para UPDATE deve virdocker > 1.7.0

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

umount
fonte
Estou executando o docker 1.3.2 build 39fa2fa (mais recente) e não vejo vestígios --uidmapnem as --private-uidsopções. Parece que o PR não conseguiu e não foi mesclado.
Leo Gallucci 28/11
Não é mesclar no núcleo, se você quiser, pode usá-lo como patch. Agora, apenas é possível restringir alguns recursos e executar seu aplicativo no contêiner a partir de um usuário não raiz.
umount
Junho de 2015 e não vejo isso mesclado no docker 1.6.2. Sua resposta ainda é válida?
Leo Gallucci
11
Problema ainda em aberto. O desenvolvedor deve adicionar suporte na versão 1.7. (- opção root) github.com/docker/docker/pull/12648
umount
2
Parece que os desenvolvedores mais uma vez moveram o lançamento com essa funcionalidade. O desenvolvedor do Docker "icecrime" diz"We apparently do have so some of conflicting designs between libnetwork and user namespaces ... and something we'd like to get in for 1.8.0. So don't think we're dropping this, we're definitely going to take a break after all these, and see how we need to reconsider the current design and integration of libnetwork to make this possible. Thanks!" github.com/docker/docker/pull/12648 Então, acho que devemos esperar a próxima versão estável.
umount
4

Minha abordagem é detectar o UID / GID atual, criar esse usuário / grupo dentro do contêiner e executar o script sob ele. Como resultado, todos os arquivos que ele criará corresponderão ao usuário no host (que é o 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"
muni764
fonte
3

Imagem Base

Use esta imagem: https://hub.docker.com/r/reduardo7/docker-host-user

ou

Importante: isso destrói a portabilidade do contêiner entre hosts .

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) Construa com docker

4) Corra!

sh run.sh
Eduardo Cuomo
fonte
0

No meu caso específico, eu estava tentando criar meu pacote de nós com a imagem da janela de encaixe para que não precisasse instalar o npm no servidor de implementação. Funcionou bem até que, fora do contêiner e na máquina host, tentei mover um arquivo para o diretório node_modules que a imagem da janela de encaixe do nó havia criado, para a qual me foi negada a permissão porque pertencia à raiz. Percebi que poderia solucionar isso copiando o diretório do contêiner para a máquina host. Através dos documentos do docker ...

Os arquivos copiados para a máquina local são criados com o UID: GID do usuário que chamou o comando docker cp.

Este é o código bash que eu usei para alterar a propriedade do diretório criado por e dentro do contêiner do 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

Se necessário, você pode remover o diretório com um segundo contêiner de janela de encaixe.

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

Para compartilhar uma pasta entre o host do docker e o contêiner, tente o comando abaixo

$ docker executar -v "$ (pwd): $ (pwd)" -i -t ubuntu

O sinalizador -v monta o diretório de trabalho atual no contêiner. Quando o diretório do host de um volume montado por ligação não existir, o Docker criará automaticamente esse diretório no host para você,

No entanto, existem 2 problemas que temos aqui:

  1. Você não pode gravar no volume montado se fosse um usuário não raiz, porque o arquivo compartilhado pertencerá a outro usuário no host,
  2. Você não deve executar o processo dentro de seus contêineres como raiz, mas mesmo se você executar como um usuário codificado, ele ainda não corresponderá ao usuário no seu laptop / Jenkins,

Solução:

Contêiner: crie um usuário, diga 'testuser', por padrão, o ID do usuário começará em 1000,

Anfitrião: crie um grupo, digamos 'testgroup', com o ID do grupo 1000 e mostre o diretório para o novo grupo (testgroup

Praveen Muthusamy
fonte
-5

Se você estiver usando o Docker Compose, inicie o contêiner no modo anterior:

wordpress:
    image: wordpress:4.5.3
    restart: always
    ports:
      - 8084:80
    privileged: true
Renato Alencar
fonte
2
Isso pode facilitar a montagem de volumes, mas o Wordpress foi lançado no modo de privilégio? Essa é uma ideia horrível - que está pedindo para ser comprometida. wpvulndb.com/wordpresses/453
Colin Harrington