Docker e --userns-remap, como gerenciar as permissões de volume para compartilhar dados entre o host e o contêiner?

97

No docker, os arquivos criados dentro de contêineres tendem a ter propriedade imprevisível durante a inspeção do host. O proprietário dos arquivos em um volume é root (uid 0) por padrão, mas assim que contas de usuário não root estão envolvidas no contêiner e gravando no sistema de arquivos, os proprietários se tornam mais ou menos aleatórios da perspectiva do host.

É um problema quando você precisa acessar os dados de volume do host usando a mesma conta de usuário que está chamando os comandos docker.

Soluções alternativas típicas são

  • forçar uIDs de usuários no momento da criação em Dockerfiles (não portátil)
  • passando o UID do usuário host para o docker runcomando como uma variável de ambiente e, em seguida, executando alguns chowncomandos nos volumes em um script de ponto de entrada.

Ambas as soluções podem fornecer algum controle sobre as permissões reais fora do contêiner.

Eu esperava que os namespaces do usuário fossem a solução final para esse problema. Executei alguns testes com a versão 1.10 lançada recentemente e --userns-remap definido para minha conta de desktop. No entanto, não tenho certeza se isso pode tornar mais fácil lidar com a propriedade de arquivos em volumes montados, infelizmente poderia ser o contrário.

Suponha que eu inicie este contêiner básico

docker run -ti -v /data debian:jessie /bin/bash
echo 'hello' > /data/test.txt
exit

Em seguida, inspecione o conteúdo do host:

ls -lh /var/lib/docker/100000.100000/volumes/<some-id>/_data/

-rw-r--r-- 1 100000 100000 6 Feb  8 19:43 test.txt

Este número '100000' é um sub-UID do meu usuário host, mas como não corresponde ao UID do meu usuário, ainda não consigo editar o test.txt sem privilégios. Este subusuário não parece ter nenhuma afinidade com meu usuário regular real fora do docker. Não está mapeado de volta.

As soluções alternativas mencionadas anteriormente nesta postagem, que consistiam em alinhar UIDs entre o host e o contêiner, não funcionam mais devido ao UID->sub-UIDmapeamento que ocorre no namespace.

Então, há uma maneira de executar o docker com o namespace do usuário habilitado (para maior segurança), enquanto ainda possibilita que o usuário host que executa o docker possua os arquivos gerados nos volumes?

Stéphane C.
fonte
Eu acho que se você vai compartilhar volumes entre o host e o contêiner, os namespaces de usuário não farão parte da solução. Sua segunda opção ("passar o UID do usuário host para o comando docker run como uma variável de ambiente e, em seguida, executar alguns comandos chown nos volumes em um script de ponto de entrada") é provavelmente a melhor solução.
larsks
4
O próprio Docker não parece encorajar o uso de volumes graváveis ​​montados em host. Como não estou executando um serviço de nuvem e apenas usando minhas próprias imagens confiáveis, agora estou me perguntando se o benefício de segurança do usuário NS vale a pena sacrificar tanto a conveniência.
Stéphane C.
@ StéphaneC. você encontrou uma abordagem melhor, talvez?
Oitenta
4
Infelizmente não, não usar o namespace do usuário e passar UIDs do host ainda é minha opção de escolha. Espero que haja uma maneira adequada de mapear usuários no futuro. Duvido, mas ainda assim, fico de olho aberto.
Stéphane C.

Respostas:

46

Se você pode pré-organizar usuários e grupos com antecedência, então é possível atribuir UIDs e GIDs de maneira específica para que os usuários do host correspondam aos usuários com namespace dentro dos contêineres.

Aqui está um exemplo (Ubuntu 14.04, Docker 1.10):

  1. Crie alguns usuários com IDs numéricos fixos:

    useradd -u 5000 ns1
    
    groupadd -g 500000 ns1-root
    groupadd -g 501000 ns1-user1
    
    useradd -u 500000 -g ns1-root ns1-root
    useradd -u 501000 -g ns1-user1 ns1-user1 -m
    
  2. Edite manualmente intervalos de IDs subordinados gerados automaticamente em arquivos /etc/subuide /etc/subgid:

    ns1:500000:65536
    

    (observe que não há registros para ns1-roote ns1-user1devido a MAX_UIDe MAX_GIDlimites em /etc/login.defs)

  3. Habilite namespaces de usuário em /etc/default/docker:

    DOCKER_OPTS="--userns-remap=ns1"
    

    Reinicie o daemon service docker restart, certifique-se de que o /var/lib/docker/500000.500000diretório seja criado.

    Agora, dentro dos contêineres você tem roote user1, e no host - ns1-roote ns1-user1, com IDs correspondentes

    ATUALIZAÇÃO: para garantir que usuários não root tenham IDs fixos em contêineres (por exemplo, usuário1 1000: 1000), crie-os explicitamente durante a construção da imagem.

Passeio de teste:

  1. Prepare um diretório de volume

    mkdir /vol1
    chown ns1-root:ns1-root /vol1
    
  2. Experimente de um contêiner

    docker run --rm -ti -v /vol1:/vol1 busybox sh
    echo "Hello from container" > /vol1/file
    exit
    
  3. Experimente com o anfitrião

    passwd ns1-root
    login ns1-root
    cat /vol1/file
    echo "can write" >> /vol1/file
    

Não é portátil e parece um hack, mas funciona.

Amartynov
fonte
3
Muito interessante e merece um +1. Mas você ainda precisa se certificar de que o UID 1000 é atribuído ao usuário1 em sua imagem. Caso contrário, você não pode ter certeza de que receberá o UID 501000 no host. A propósito, temos absoluta certeza de que a fórmula é sempre subUID lower bound + UID in imagese estivermos executando muitas imagens diferentes com um usuário com ID definido como 1000?
Stéphane C.
@ StéphaneC. Bom ponto! Adicionada uma nota sobre como corrigir IDs dentro de imagens. Quanto à fórmula, vou fazer mais experiências com minhas próprias imagens e atualizarei a resposta se encontrar alguma coisa
amartynov
1
Se você organizar usuários e grupos manualmente no host e em contêineres, você realmente precisa do recurso "namespace do usuário"?
Tristan
1
O namespace que você cria separa os usuários hosts dos usuários de container, mas você pode precisar de vários namespaces para containers, especialmente quando imagens oficiais (como a do mysql) criam um usuário sem uid explícito. Como você lida com vários namespaces quando a opção --userns-remap espera apenas um?
Tristan
2
@amartynov Posso perguntar por que você se deu ao trabalho de especificar um UID (5000) para o usuário "ns1"? Visto que é o nome (não o UID) que você faz referência nos arquivos subuid e subgid, parece que não importa qual UID esse usuário recebe. Estou perdendo algum relacionamento, como a semelhança entre 5.000 e 500.000 pode sugerir?
Jollymorphic
3

Uma solução alternativa é atribuir dinamicamente o uid do usuário no tempo de construção para corresponder ao host.

Exemplo Dockerfile:

FROM ubuntu
# Defines argument which can be passed during build time.
ARG UID=1000
# Create a user with given UID.
RUN useradd -d /home/ubuntu -ms /bin/bash -g root -G sudo -u $UID ubuntu
# Switch to ubuntu user by default.
USER ubuntu
# Check the current uid of the user.
RUN id
# ...

Em seguida, construa como:

docker build --build-arg UID=$UID -t mycontainer .

e execute como:

docker run mycontainer

Se você já tiver um contêiner, crie um contêiner de wrapper com o seguinte Dockerfile:

FROM someexistingcontainer
ARG UID=1000
USER root
# This assumes you've the existing user ubuntu.
RUN usermod -u $UID ubuntu
USER ubuntu

Isso pode ser embrulhado docker-compose.ymlcomo:

version: '3.4'
services:
  myservice:
    command: id
    image: myservice
    build:
      context: .
    volumes:
    - /data:/data:rw

Em seguida, crie e execute como:

docker-compose build --build-arg UID=$UID myservice; docker-compose run myservice
Kenorb
fonte
1
Eu não gostaria de parecer hostil, mas esta é na verdade uma das soluções alternativas listadas na pergunta original, mas não é uma solução e não está relacionada a namespaces de usuário.
Stéphane C.
@ StéphaneC. Você pode comentar sobre esta questão relacionada? stackoverflow.com/questions/60274418/…
overexchange
-1

Você pode evitar problemas de permissão usando o docker cpcomando .

A propriedade é definida para o usuário e o grupo principal no destino. Por exemplo, os arquivos copiados para um container são criados com UID:GIDo usuário root. Os arquivos copiados para a máquina local são criados com o UID:GIDdo usuário que invocou o docker cpcomando.

Aqui está seu exemplo alterado para usar docker cp:

$ docker run -ti -v /data debian:jessie /bin/bash
root@e33bb735a70f:/# echo 'hello' > /data/test.txt
root@e33bb735a70f:/# exit
exit
$ docker volume ls
DRIVER              VOLUME NAME
local               f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93
$ sudo ls -l /var/lib/docker/100000.100000/volumes/f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93/_data
total 4
-rw-r--r-- 1 100000 100000 6 Oct  6 10:34 test.txt
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
e33bb735a70f        debian:jessie       "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       determined_hypatia
$ docker cp determined_hypatia:/data/test.txt .
$ ls -l test.txt 
-rw-r--r-- 1 don don 6 Oct  6 10:34 test.txt
$ cat test.txt
hello
$ 

No entanto, se você deseja apenas ler os arquivos de um contêiner, não precisa do volume nomeado. Este exemplo usa um contêiner nomeado em vez de um volume nomeado:

$ docker run -ti --name sandbox1 debian:jessie /bin/bash
root@93d098233cf3:/# echo 'howdy' > /tmp/test.txt
root@93d098233cf3:/# exit
exit
$ docker cp sandbox1:/tmp/test.txt .
$ ls -l test.txt
-rw-r--r-- 1 don don 6 Oct  6 10:52 test.txt
$ cat test.txt
howdy
$ 

Considero os volumes nomeados úteis quando desejo copiar arquivos em um contêiner, conforme descrito nesta pergunta .

Don Kirkby
fonte
Mas docker cpenvolve a duplicação de dados. Além disso, de acordo com o documento, ao copiar dados para um contêiner, ele define os IDs de propriedade de acordo com o usuário root, que muitas vezes não é a conta que executa o aplicativo em contêiner. Não consigo ver como isso resolve nosso problema.
Stéphane C.
Você está certo, @ Stéphane, envolve a duplicação de dados. No entanto, fazer cópias dos arquivos permite atribuir diferentes propriedades e permissões no host e no contêiner. docker cpoferece controle total sobre a propriedade do arquivo ao transmitir um arquivo tar para dentro ou para fora de um contêiner. Você pode ajustar a propriedade e as permissões de cada entrada no arquivo tar à medida que o transmite, para que não fique limitado ao usuário root.
Don Kirkby