GNU/Linux >> Tutoriales Linux >  >> Linux

Cómo depurar problemas con volúmenes montados en contenedores sin raíz

Una de las preguntas más frecuentes que me hacen sobre Podman sin raíz es cómo depurar problemas con volúmenes montados en el contenedor. Esta pregunta es engañosamente difícil. En muchos sentidos, ejecutar Podman sin root es casi idéntico a ejecutarlo como root . Desafortunadamente, esto no siempre es cierto, y los volúmenes son una de las áreas con las diferencias más significativas. Aquí, explicaré en detalle cuáles son estas diferencias, qué tipos de errores pueden causar y cómo puede solucionarlos. Para comenzar, necesitamos información básica sobre cómo funcionan los contenedores sin raíz, comenzando con una de las características más fundamentales de Podman sin raíz:espacios de nombres de usuario .

El espacio de nombres de usuario

Una de las características de seguridad fundamentales de los contenedores son los espacios de nombres del kernel de Linux. Un espacio de nombres es una forma de aislar un proceso (o grupo de procesos) del resto del sistema al limitar lo que puede ver. Hay muchos espacios de nombres diferentes, cada uno con un efecto diferente. Por ejemplo, el espacio de nombres PID limita qué PID puede ver un proceso:solo los PID dentro del espacio de nombres son visibles y se numeran independientemente del host. El proceso también tiene un PID en el host, por lo que el PID 2000 en el host podría ser el PID 1 en el espacio de nombres. En el momento de escribir este artículo, los espacios de nombres que proporciona el kernel son Mount, PID, Network, IPC, UTS, User, cgroup y time, cada uno de los cuales aísla un aspecto diferente del sistema; el que más nos importa para este blog es el espacio de nombres de usuario.

Los espacios de nombres de usuario aíslan a los usuarios y grupos disponibles en el contenedor de aquellos disponibles para el sistema host. Un espacio de nombres de usuario funciona asignando usuarios en el contenedor a usuarios en el host. Por ejemplo, podríamos asignar los usuarios 0 a 1000 en el contenedor a los usuarios 100000 a 101000 en el host (los grupos también se asignan de manera idéntica, pero nos centraremos en los usuarios por simplicidad). Esta asignación actúa de manera muy similar al espacio de nombres PID que describimos anteriormente, pero con usuarios.

Desde el host, todos los accesos desde raíz en el contenedor (UID 0) parecerá ser del UID 100000. Dentro del contenedor, cualquier archivo propiedad del usuario 100000 en el host aparecerá como propiedad del UID 0 (raíz ). Una pregunta interesante es qué sucede con los usuarios que no están asignados al contenedor:¿qué pasa si monto un volumen propiedad del usuario 1001 en el host en un contenedor usando el espacio de nombres de usuario que describí? El núcleo, en este caso, asignará cualquier UID o GID no válido en el espacio de nombres al UID/GID 65534, un usuario y grupo llamado nadie , que no es un grupo normal.

Todavía es posible interactuar con archivos propiedad de nadie si los permisos lo permiten (por ejemplo, puede leer un archivo propiedad de nadie que es de lectura mundial y escribe en ese archivo si es de escritura mundial), pero no puede cambiarle la propiedad. Los espacios de nombres de usuario también otorgan versiones limitadas de capacidades específicas que normalmente solo están disponibles para root —el ejemplo típico es que los espacios de nombres de usuario pueden montar ciertos tipos de sistemas de archivos, como tmpfs.

Los espacios de nombres de usuario son extremadamente útiles porque nos permiten actuar como root dentro del contenedor sin ser realmente root en el sistema Podemos usar espacios de nombres de usuario para separar contenedores de diferentes usuarios en sistemas de múltiples usuarios:los contenedores de un usuario se ejecutarían como UID 10000 a 10999, los contenedores de otro como 11000 a 11999, y así sucesivamente. Dentro de cada contenedor, parece que la aplicación es root y a través de las capacidades limitadas otorgadas, puede realizar las operaciones más comunes (instalar paquetes, por ejemplo).

Sin embargo, supongamos que una aplicación logra salir del contenedor. En ese caso, no se ejecuta como root en el sistema y no se ejecuta con el mismo UID y GID que los contenedores de cualquier otro usuario; la capacidad de atacar diferentes partes del sistema es extremadamente limitada.

Sin embargo, su adopción estuvo limitada por limitaciones técnicas, en particular, el hecho de que hasta hace muy poco no había forma de reasignar UID y GID a nivel del sistema de archivos. Para tener un contenedor usando los UID 10000 a 10999, tuvimos que hacer una copia de su imagen y luego chown cada UID en dicha imagen agregando 10000 al UID existente. Este chown puede ser muy lento y (en muchos sistemas de archivos) aumenta drásticamente la cantidad de espacio requerido.

[ ¿Empezando con los contenedores? Consulta este curso gratuito. Implementación de aplicaciones en contenedores:una descripción técnica general. ]

Contenedores sin raíz

Donde los espacios de nombres de usuario se han vuelto extremadamente útiles y populares son los contenedores sin raíz. Un usuario no root en Linux tiene acceso a un solo UID y GID (el suyo propio). Sin embargo, los contenedores esperan acceder a más de un usuario y grupo; muchos archivos en las imágenes del contenedor no son propiedad de root. y las aplicaciones a menudo se ejecutarán como un usuario no root en el contenedor para imponer la separación de privilegios dentro del contenedor.

Para algunos entornos (computación de alto rendimiento, HPC, siendo uno notable), tener un solo usuario y grupo en el contenedor es aceptable (incluso deseable). Para la mayoría de los otros entornos, un usuario y grupo es una limitación severa para la utilidad del contenedor. Podemos usar espacios de nombres de usuario para obtener estos usuarios y grupos adicionales que necesitamos para actuar como un contenedor típico.

Sin embargo, necesitamos privilegios elevados para lograr estos usuarios y grupos adicionales. Esto es lo que dice el newuidmap y newgidmap ejecutables (y el /etc/subuid y /etc/subgid archivos de configuración que leen) lo hacen:nos otorgan acceso a un bloque de usuarios y grupos, que luego se asignan a un espacio de nombres de usuario para que los usen los contenedores sin raíz. Las limitaciones de los espacios de nombres de usuario con respecto a la compatibilidad con el sistema de archivos aún se aplican hasta cierto punto, pero se mitigan al tener solo un conjunto único de UID y GID para que use cada usuario.

También cabe destacar el hecho de que el kernel manejará automáticamente el chown operación para nosotros si desempaquetamos la imagen dentro de un espacio de nombres de usuario. El cambio de usuario del espacio de nombres garantiza que se asigne el UID correcto en la creación en lugar de requerir que el tiempo de ejecución del contenedor lo configure manualmente.

Las capacidades adicionales de un espacio de nombres de usuario también son esenciales para algunas de las cosas que necesitan los contenedores sin raíz:sin la capacidad de montar los sistemas de archivos FUSE y tmpfs, los contenedores sin raíz serían mucho más limitados (hasta el punto de ser casi inutilizables).

Espacios de nombres de usuario en Podman

Ahora que entendemos cómo funcionan los espacios de nombres de usuario en general, analicemos cómo se implementan en Podman sin raíz.

Todos los contenedores Podman sin raíz se ejecutan en un espacio de nombres de usuario, incluso si el usuario no tiene más de un UID y GID disponibles. Todos los contenedores de un usuario comparten un único espacio de nombres de usuario, que se mantiene abierto mediante el proceso de pausa sin raíz. Los espacios de nombres generalmente son podados por el kernel cuando no hay más procesos en ellos, por lo que mantener un proceso que no hace más que dormir y nunca salir mantendrá vivo el espacio de nombres de usuario.

Lo primero que hace un proceso de Podman sin raíz es unirse al espacio de nombres de usuario sin raíz (o crear un nuevo espacio de nombres y pausar el proceso si aún no existen). Como parte de la creación del espacio de nombres de usuario, Podman ejecutará el newuidmap y newgidmap ejecutables para otorgar UID y GID adicionales que el usuario haya asignado en /etc/subuid y /etc/subgid (la cantidad predeterminada otorgada en la creación del usuario es 65536 de cada uno). Puede ver las asignaciones de usuarios disponibles en podman info comando, en el idMappings campo:

mheon@podman-rhel8-test $ podman info
  idMappings:
    gidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 100000
      size: 65536
    uidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 100000
      size: 65536

Tenga en cuenta que el espacio de nombres de usuario sin raíz no se vuelve a crear de forma predeterminada si /etc/subuid y /etc/subgid se cambian los archivos; esto se hace ejecutando el podman system migrate dominio. Si ha editado estos archivos y Podman no parece reconocer sus cambios, ejecute este comando.

Todos los comandos de Podman sin raíz se ejecutan dentro del espacio de nombres de usuario sin raíz creado para garantizar que tengamos las asignaciones y los privilegios de usuario correctos. Incluso comandos informativos simples, como podman info , requieren el espacio de nombres de usuario sin raíz. Como tal, un espacio de nombres de usuario no funcional es un obstáculo para Podman sin raíz. Afortunadamente, esto no sucede a menudo. Cuando lo hace, generalmente encontramos que es causado por permisos de archivo insuficientes en el newuidmap y/o newgidmap binarios en el sistema (falta una capacidad de archivo, por lo general). Reinstalando el paquete que los contiene (llamado shadow-utils en RHEL, CentOS y Fedora) generalmente resolverá esto.

Una vez que se ha creado el espacio de nombres de usuario sin raíz, podemos comenzar a ejecutar contenedores. El uso de volúmenes con estos contenedores es el primer punto en el que la mayoría de los usuarios encontrarán las diferencias prácticas entre Podman con raíz y sin raíz. El usuario ejecutará un contenedor con un volumen montado y pronto descubrirá que el contenedor no puede acceder a los archivos del volumen, a pesar de que aparentemente todo está configurado correctamente.

Por ejemplo, examinemos un comando Podman simple:

podman run --user 1000:1000 -v /home/mheon/data:/data:Z ubi8 sh

Este comando está siendo ejecutado por mi usuario, mheon , con UID y GID configurados en 1000 (el mismo usuario que se le indicó que usara el contenedor). A mi usuario se le han asignado 65536 UID y GID a partir de UID/GID 100000 a través de /etc/subuid y /etc/subgid . El contexto de SELinux se ha configurado para que el contenedor pueda acceder al directorio a través de :Z opción en el montaje de volumen.

Sin embargo, el acceso a los /data se denegará la carpeta en el contenedor y el único mensaje de error que devolverá el sistema es un permiso denegado genérico del núcleo. Cualquier usuario que no supiera qué es un espacio de nombres de usuario no tendría idea de qué está mal.

Sin embargo, ahora que lo sabemos, la causa debería ser obvia:la asignación de usuarios significa que el UID 1000 en el contenedor no es en realidad el UID 1000 en el host. Puede ver el usuario en el contenedor y el usuario real en el host a través de podman top comando:

mheon@podman-rhel8-test $ podman top -l user,huser
USER   HUSER
1000   100999

Aquí, USUARIO es el usuario en el contenedor, mientras que HUSER es el usuario en el host.

Aun así, saber por qué sucede algo no significa que sepamos cómo solucionarlo. Todavía queremos ejecutar nuestro contenedor Podman sin raíz con un volumen específico montado en él. ¿Como hacemos eso? Afortunadamente, hay muchas maneras de solucionar esto, que trataré a continuación.

La primera solución

El primero es simple:El --user La opción se puede omitir del contenedor, ejecutando el comando del contenedor como root . Como se indicó anteriormente, de forma predeterminada, Podman asigna al usuario que ejecuta el contenedor a raíz en el contenedor, por lo que ahora accederemos al volumen como UID/GID 1000 en el host, a pesar de ser root en el contenedor Ejecutándose como root en un contenedor raíz es un posible problema de seguridad ya que se está ejecutando como raíz del sistema usuario:si un atacante escapa del contenedor, podría actuar como raíz en el sistema El objetivo de un contenedor sin raíz es que esto nunca es cierto:los problemas de seguridad son principalmente un factor secundario.

Desafortunadamente, la solución de ejecutar el contenedor como root falla cuando la imagen está escrita específicamente para usar un usuario no root. Algunas imágenes de contenedores presentan secuencias de comandos de punto de entrada complejas para descartar permisos que no se pueden modificar fácilmente. Estos requerirán una solución alternativa.

La segunda solución

La segunda opción es otorgar al usuario que se ejecuta en el contenedor permiso para leer y escribir en la carpeta montada desde el host. A partir de Podman v3.1.0, esto se puede hacer automáticamente a través de :U opción de volumen al -v bandera (por ejemplo, -v /home/mheon/data:/data:Z,U ).

Luego ingrese podman unshare chown 1000:1000 /home/mheon/data . Esta opción de volumen ajustará automáticamente la propiedad del directorio, por lo que el usuario que se ejecuta en el contenedor (cualquiera que sea el UID de usuario 1000 en el contenedor que esté asignado al host) será el propietario del directorio. En versiones sin esta marca, podman unshare El comando se puede usar para ingresar el espacio de nombres de usuario sin raíz y luego chown el directorio será propiedad del usuario que ejecuta el contenedor.

En este caso, podman unshare chown 1000:1000 /home/mheon/data cambiaría la propiedad del directorio en el host al usuario y grupo que se asignan a UID/GID 1000 en el espacio de nombres de usuario. Tenga en cuenta que, si se cambia la propiedad, todos los directorios principales en el host también requerirán el permiso de ejecución para todos los usuarios (chmod a+x… ) para garantizar que se pueda acceder al directorio en cuestión.

Desafortunadamente, el chown enfoque viene con su propio conjunto de desventajas. El /home/mheon/data El directorio está en el directorio de inicio de mi usuario, pero ya no es propiedad de mi usuario (en este caso, es propiedad del usuario y el grupo 100999 ). En el espacio de nombres de usuario sin raíz, mheon el usuario puede actuar como root y leer, escribir y modificar archivos propiedad de ese usuario; pero no puede hacer ninguna de estas cosas fuera de él. Podman proporciona un comando para ingresar un shell dentro del espacio de nombres de usuario sin raíz (podman unshare ) que se puede usar para modificar o eliminar dichos archivos, pero la incapacidad de administrar estos archivos de otra manera es un inconveniente.

La tercera solución

La tercera opción es usar el --userns=keep-id opción para podman run . Esta bandera le dice a Podman que haga dos cosas:primero, configurar el usuario que ejecuta el contenedor como el UID y el GID del usuario que ejecutó Podman (a menos que lo anule explícitamente el --user y segundo, para reordenar los usuarios asignados al contenedor de modo que el usuario que ejecutó Podman esté asignado a su propio UID y GID, en lugar de raíz (Esto se hace a través de un segundo espacio de nombres de usuario, anidado dentro del espacio de nombres de usuario sin raíz, creado solo para este contenedor). El usuario en el espacio de nombres de usuario en el que se ejecuta el contenedor no es root pero aún se asigna al usuario que ejecuta Podman (mheon ) en el host y, por lo tanto, puede acceder al directorio montado en el ejemplo /home/mheon/data .

Sin embargo, esto no resolverá todos los errores de acceso. Otro problema común es intentar montar en un archivo o dispositivo al que tiene acceso el usuario que ejecuta Podman, pero solo por un grupo complementario. Por ejemplo, digamos que mi usuario, mheon , es parte del kvm grupo propietario de /dev/kvm , y elijo montar /dev/kvm en un contenedor usando podman run -t -i -v /dev/kvm:/dev/kvm fedora bash . El contenedor no podrá acceder a /dev/kvm , a pesar de que se está ejecutando como mi usuario en el host (que debería tener acceso).

El motivo es que, por motivos de seguridad, los contenedores eliminarán (de forma predeterminada) todos los grupos adicionales como parte de su creación. Este comportamiento se puede deshabilitar, pero solo usando el crun Tiempo de ejecución de OCI (esto debería ser predeterminado a partir de Podman 3.0 en todas las distribuciones excepto RHEL) pasando una anotación especial (--annotation run.oci.keep_original_groups=1 ).

En el próximo Podman v3.2.0, esto estará disponible a través de un argumento especial para group-add flag (--group-add keep-groups ). Tenga en cuenta que, si bien podemos conservar el acceso a estos grupos, no tenemos permiso para agregarlos al espacio de nombres de usuario sin raíz; solo podemos hacerlo con los usuarios y grupos que se nos asignen en /etc/subuid y /etc/subgid . Un ls -al en /dev/kvm en el contenedor encontrará que es propiedad de nadie:nadie (como su propietario y grupo real, root:kvm , no están mapeados en el contenedor).

Sin embargo, se puede acceder porque, a pesar de que el kvm El grupo no es parte del espacio de nombres del usuario, el proceso del contenedor sigue siendo parte del grupo a los ojos del núcleo. Esto es algo limitante ya que no es posible crear archivos explícitamente como uno de estos grupos complementarios (como nadie no es un grupo real con el que podamos interactuar), pero es suficiente para dar al contenedor acceso al contenido en el host al que de otro modo no podría acceder, y los directorios con el bit SUID propiedad de un grupo complementario aún establecerán el propietario correcto .

Otro tipo de error común se encuentra al extraer imágenes con archivos o carpetas que pertenecen a usuarios de UID alto. Cualquier archivo o carpeta propiedad de un UID o GID demasiado grande para incluirse en el espacio de nombres del usuario generará un error. Anteriormente escribí un blog sobre esto y posibles soluciones, que se pueden encontrar aquí.

Conclusión

Una de las características más sólidas de Podman es nuestro fuerte apoyo a los contenedores sin raíz, y no es difícil ver por qué la gente está emocionada. Los contenedores sin raíz son fáciles de configurar, más seguros que root contenedores, y puede hacer casi cualquier cosa que un contenedor ejecute como root puede hacer. Por supuesto, la palabra clave es casi — debido a que la experiencia general con root y rootless es tan similar, las diferencias pueden ser confusas y, a menudo, no son fáciles de explicar. Después de leer este blog, debería tener una sólida comprensión de una de las mayores de estas diferencias y cómo trabajar con Podman para que sus contenedores funcionen de la manera que desea.


Linux
  1. Cómo eliminar cuentas de usuario con el directorio de inicio en Linux

  2. Cómo agregar soporte de kernel PPP a contenedores OpenVZ

  3. Agregar usuario a grupo en Linux, cómo hacerlo (con ejemplos)

  4. ¿Cómo crear un nuevo usuario con acceso Ssh?

  5. Cómo listar contenedores Docker

Cómo ejecutar contenedores como servicio Systemd con Podman

Cómo Cirrus CLI usa Podman para lograr compilaciones sin raíz

Cómo editar código en contenedores Docker con Visual Studio Code

Cómo ejecutar contenedores Docker

Cómo:Introducción a los contenedores de Windows y Docker

Cómo gestionar contenedores Docker