Docker y --userns-remap, ¿cómo administrar los permisos de volumen para compartir datos entre el host y el contenedor?

96

En la ventana acoplable, los archivos creados dentro de los contenedores tienden a tener una propiedad impredecible al inspeccionarlos desde el host. El propietario de los archivos en un volumen es root (uid 0) por defecto, pero tan pronto como las cuentas de usuario no root están involucradas en el contenedor y escriben en el sistema de archivos, los propietarios se vuelven más o menos aleatorios desde la perspectiva del host.

Es un problema cuando necesita acceder a los datos de volumen desde el host utilizando la misma cuenta de usuario que llama a los comandos de la ventana acoplable.

Las soluciones típicas son

  • forzar los UID de los usuarios en el momento de la creación en Dockerfiles (no portátil)
  • pasar el UID del usuario del host al docker runcomando como una variable de entorno y luego ejecutar algunos chowncomandos en los volúmenes en un script de punto de entrada.

Ambas soluciones pueden dar cierto control sobre los permisos reales fuera del contenedor.

Esperaba que los espacios de nombres de usuario fueran la solución final a este problema. He ejecutado algunas pruebas con la versión 1.10 publicada recientemente y --userns-remap configurado en mi cuenta de escritorio. Sin embargo, no estoy seguro de que pueda hacer que la propiedad de archivos en volúmenes montados sea más fácil de manejar, me temo que en realidad podría ser lo contrario.

Supongamos que comienzo este contenedor básico

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

Y luego inspecciona el contenido del 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' es un sub-UID de mi usuario de host, pero como no se corresponde con el UID de mi usuario, todavía no puedo editar test.txt sin privilegios. Este subusuario no parece tener ninguna afinidad con mi usuario habitual real fuera de Docker. No está mapeado.

Las soluciones alternativas mencionadas anteriormente en esta publicación, que consistían en alinear los UID entre el host y el contenedor, ya no funcionan debido a la UID->sub-UIDasignación que se produce en el espacio de nombres.

Entonces, ¿hay alguna manera de ejecutar la ventana acoplable con el espacio de nombres de usuario habilitado (para mejorar la seguridad), y al mismo tiempo hacer posible que el usuario host que ejecuta la ventana acoplable posea los archivos generados en los volúmenes?

Stéphane C.
fuente
Creo que si va a compartir volúmenes entre el host y el contenedor, los espacios de nombres de usuario no serán parte de la solución. Su segunda opción ("pasar el UID del usuario del host al comando docker run como una variable de entorno y luego ejecutar algunos comandos chown en los volúmenes en un script de punto de entrada") es probablemente la mejor solución.
larsks
4
Docker en sí no parece fomentar el uso de volúmenes grabables montados en el host. Dado que no estoy ejecutando un servicio en la nube y solo uso mis propias imágenes de confianza, ahora me pregunto si vale la pena sacrificar tanta conveniencia por el beneficio de seguridad del usuario NS.
Stéphane C.
@ StéphaneC. ¿Ha encontrado quizás un enfoque mejor?
EightyEight
4
Desafortunadamente no, no usar el espacio de nombres de usuario y pasar los UID del host sigue siendo mi opción preferida. Espero que haya una forma adecuada de mapear usuarios en el futuro. Lo dudo, pero aún así, mantengo un ojo abierto.
Stéphane C.

Respuestas:

46

Si puede preorganizar usuarios y grupos con anticipación, entonces es posible asignar UID y GID de una manera tan específica para que los usuarios del host se correspondan con los usuarios con espacios de nombres dentro de los contenedores.

Aquí hay un ejemplo (Ubuntu 14.04, Docker 1.10):

  1. Cree algunos usuarios con ID numéricos fijos:

    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 los rangos de ID subordinados generados automáticamente en archivos /etc/subuidy /etc/subgid:

    ns1:500000:65536
    

    (tenga en cuenta que no hay registros para ns1-rooty ns1-user1debido MAX_UIDy MAX_GIDlímites en /etc/login.defs)

  3. Habilite los espacios de nombres de usuario en /etc/default/docker:

    DOCKER_OPTS="--userns-remap=ns1"
    

    Reinicie el demonio service docker restart, asegúrese de que /var/lib/docker/500000.500000se crea el directorio.

    Ahora, dentro de los contenedores tiene rooty user1, y en el host, ns1-rooty ns1-user1, con ID coincidentes

    ACTUALIZACIÓN: para garantizar que los usuarios no root tengan ID fijos en los contenedores (por ejemplo, user1 1000: 1000), créelos explícitamente durante la creación de la imagen.

Prueba de conducción:

  1. Prepare un directorio de volumen

    mkdir /vol1
    chown ns1-root:ns1-root /vol1
    
  2. Pruébalo en un recipiente

    docker run --rm -ti -v /vol1:/vol1 busybox sh
    echo "Hello from container" > /vol1/file
    exit
    
  3. Prueba del anfitrión

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

No es portátil y parece un truco, pero funciona.

Amartynov
fuente
3
Muy interesante y merece un +1. Pero aún debe asegurarse de que al usuario1 de su imagen se le asigne el UID 1000. De lo contrario, no puede estar seguro de que recibirá el UID 501000 en el host. Por cierto, ¿estamos absolutamente seguros de que la fórmula siempre es subUID lower bound + UID in imagesi estamos ejecutando muchas imágenes diferentes con un usuario que tiene el ID configurado en 1000?
Stéphane C.
@ StéphaneC. ¡Buen punto! Se agregó una nota sobre cómo corregir las identificaciones dentro de las imágenes. En cuanto a la fórmula, voy a experimentar más con mis propias imágenes y actualizaré la respuesta si encuentro algo
amartynov
1
Si organiza a los usuarios y grupos a mano en el host y en contenedores, ¿realmente necesita la función de "espacio de nombres de usuario"?
Tristan
1
El espacio de nombres que crea separa a los usuarios de hosts de los usuarios de contenedores, pero es posible que necesite varios espacios de nombres para los contenedores, especialmente cuando las imágenes oficiales (como la de mysql) crean un usuario sin uid explícito. ¿Cómo maneja varios espacios de nombres cuando la opción --userns-remap espera solo uno?
Tristan
2
@amartynov ¿Puedo preguntar por qué se tomó la molestia de especificar un UID (5000) para su usuario "ns1"? Dado que es el nombre (no el UID) al que hace referencia en los archivos subuid y subgid, parece que no debería importar qué UID obtiene este usuario. ¿Me falta alguna relación, como podría sugerir la similitud entre 5000 y 500000?
Jollymorphic
2

Una solución es asignar dinámicamente el uid del usuario en el tiempo de compilación para que coincida con el host.

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

Luego construye como:

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

y ejecutar como:

docker run mycontainer

Si tiene un contenedor existente, cree un contenedor de envoltura con lo siguiente Dockerfile:

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

Esto se puede envolver docker-compose.ymlcomo:

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

Luego compile y ejecute como:

docker-compose build --build-arg UID=$UID myservice; docker-compose run myservice
Kenorb
fuente
1
No quisiera parecer antipático, pero esta es en realidad una de las soluciones enumeradas en la pregunta original, pero no es una solución y no está relacionada con los espacios de nombres de los usuarios.
Stéphane C.
@ StéphaneC. ¿Puedes comentar sobre esta pregunta relacionada? stackoverflow.com/questions/60274418/…
sobreexchange
-1

Puede evitar problemas de permisos utilizando el docker cpcomando .

La propiedad se establece para el usuario y el grupo principal en el destino. Por ejemplo, los archivos copiados a un contenedor se crean con UID:GIDel usuario root. Los archivos copiados a la máquina local se crean con el nombre UID:GIDdel usuario que invocó el docker cpcomando.

Aquí está su ejemplo cambiado 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
$ 

Sin embargo, si solo desea leer archivos de un contenedor, no necesita el volumen con nombre. Este ejemplo usa un contenedor con nombre en lugar de un volumen con nombre:

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

Encuentro que los volúmenes con nombre son útiles cuando quiero copiar archivos en un contenedor, como se describe en esta pregunta .

Don Kirkby
fuente
Pero docker cpimplica la duplicación de datos. Además, según el documento, al copiar datos en un contenedor, establece los ID de propiedad según el usuario raíz, que a menudo no es la cuenta que ejecuta la aplicación en contenedor. No veo cómo resuelve nuestro problema.
Stéphane C.
Tienes razón, @ Stéphane, se trata de duplicar datos. Sin embargo, hacer copias de los archivos le permite asignar diferentes propiedades y permisos en el host y en el contenedor. docker cple brinda control total sobre la propiedad de los archivos cuando transmite un archivo tar dentro o fuera de un contenedor. Puede ajustar la propiedad y los permisos de cada entrada en el archivo tar a medida que lo transmite, por lo que no está limitado al usuario raíz.
Don Kirkby