¿Ejecuta sus contenedores como superusuario o como un usuario común? Es una pregunta engañosamente simple. Quizás se sienta tentado a responder demasiado rápido. ¿Tiene realmente claro el modelo de amenazas? Sospecho que no. El objetivo de esta publicación es aclarar estos conceptos.

Antes de poder responder a la pregunta anterior, debe determinar si estamos hablando del motor de contenedores (Podman, Docker, CRI-O, Containerd, etc.), el proceso dentro del contenedor (Apache, PostgreSQL, MySQL, etc.) o el ID de proceso al que está asignado el contenedor (los tres pueden ser diferentes). A primera vista, esto podría no ser obvio. Tanto el motor de contenedores como su subproceso en contenedores se pueden ejecutar prácticamente como cualquier usuario.

Containers illustration¿Quiere lograr más con la imagen base universal (UBI) de Red Hat?

El concepto de superusuario

Con la llegada de Podman, los contenedores sin privilegios de superusuario se convirtieron en una posibilidad real. Dado que Podman crea contenedores como subprocesos directos de sí mismo, es fácil demostrar que hay cuatro opciones posibles en las que pensar. Pero, ¿qué significa que no existan privilegios de superusuario? Para comprenderlo, debe comprender cómo funcionan los superusuarios dentro de un contenedor, y, para ello, antes debe comprender su funcionamiento por fuera de un contenedor. Hermoso trabalenguas.

En la siguiente tabla podemos ver el superusuario dentro y fuera del contenedor (gracias a Vincent Batts por explicarme tan claramente estos conceptos en DevConf.us 2019). Con este marco, debe comenzar a formarse una idea del funcionamiento sin superusuarios:

 

Table 1: Root inside and outside the container showing how users appear to the system

Señalemos algunas cosas interesantes sobre el ejemplo anterior. En primer lugar, las opciones de la línea de comandos son -i (interactivo) -t (terminal) y -u (usuario). En combinación, estas opciones dan como resultado una terminal interactiva dentro del contenedor, y especifican que el proceso en los contenedores debe ejecutarse como el usuario sync. Puede investigarlos más a fondo escribiendo el comando 'man podman-run'. 

En segundo lugar, preste mucha atención a $, el cual implica que nuestro shell se ejecuta como un usuario normal, y #, que implica que nuestro shell se ejecuta como superusuario. Esto es característico de Unix y lo ayudará a entender los superusuarios dentro y fuera de los contenedores. 

En tercer lugar y siguiendo con el ejemplo anterior, por definición, Podman está fuera del contenedor y se ejecuta como superusuario o como usuario normal (fatherlinux), mientras que, dentro del contenedor, bash se ejecuta como superusuario o como usuario normal (sync). Los usuarios en el archivo /etc/passwd del host del contenedor se usan para Podman, mientras que los del archivo /etc/passwd de la imagen del contenedor se usan en el contenedor que está en ejecución. Dicho de otra manera, estos dos archivos passwd implican que podríamos tener dos conjuntos de usuarios: uno fuera del contenedor y otro dentro de él. Este es exactamente el propósito de los espacios de nombres de usuario.

Espacios de nombres de usuario

Cuando el kernel crea un proceso en ejecución dentro de un contenedor, el espacio de nombres de usuario asigna el ID de usuario de los procesos en contenedores a un ID de usuario diferente por fuera del contenedor. Veamos:

 

Table 2: Showing user IDs and username inside and outside container

Las opciones de la línea de comandos son -i (interactivo) -d (separar) y -u (usuario). En combinación, estas opciones ejecutan el contenedor en segundo plano y especifican que el proceso en contenedor debe ejecutarse como el usuario sync. Al igual que en el caso anterior, puede investigarlos más a fondo escribiendo el comando 'man podman-run'. 

Podman también nos ofrece un subcomando realmente interesante llamado top que nos permite asignar el usuario que se encuentra en el host del contenedor al usuario en el contenedor en ejecución. El ejemplo anterior demuestra que cuando ejecutamos un contenedor como superusuario, lo que hacemos es asignar el usuario sync (uid 5) en el contenedor al usuario sync (uid 5) en el host del contenedor subyacente. Esto significa que si un proceso se separara de este contenedor, podría ejecutarse con los privilegios del verdadero usuario sync. 

Por otro lado, cuando ejecutamos exactamente el mismo contenedor, pero usando un usuario común (fatherlinux), se asigna el usuario sync (uid 5) en el contenedor en ejecución a uid 100004 en el host del contenedor subyacente. ¡Qué extraño! ¿Por qué no asignó el usuario sync (uid 5) a fatherlinux (uid 1000)?

Bueno, la respuesta corta es porque, con los kernels y los paquetes shadow-utils más nuevos (useradd, passwd, entre otros), cada nuevo usuario recibe una serie de ID de usuario. Anteriormente, en un sistema Unix, cada usuario tenía solo un ID, pero ahora, cada uno de ellos puede tener miles de UID a su disposición y usarlos dentro de los contenedores. 

Esto es útil cuando un contenedor usa varios usuarios; como, por ejemplo, cuando se ejecutan Apache y MySQL juntos en un solo contenedor o pod, o cuando se ejecutan un contenedor sidecar y un agente que se ejecuta como un usuario distinto. Pero, ¿de dónde proviene esta asignación? Desde dos archivos: /etc/subuidy  /etc/subgid. Cada vez que se agregan usuarios, se crean entradas en estos archivos, ya sea que un administrador de sistemas lo realice manualmente, o que se haga a través del comando usermod.

Análisis en profundidad opcional sobre los identificadores de usuario

El siguiente es un ejemplo de las entradas en mi sistema. Con las siguientes entradas, el usuario fatherlinux puede asignar hasta 65 535 ID de usuario en contenedores a ID de usuario reales en el sistema a partir de 100 000. De forma predeterminada, el intervalo de ID de usuario shadow-utils (useradd, passwd, y otros) está reservado para un solo usuario. El comando useradd reservará el siguiente intervalo para el próximo usuario. En este ejemplo, es el usuario fred, que comienza con el ID de usuario 165536:

cat /etc/subuid fatherlinux:100000:65536 fred:165536:65536 cat /etc/subgid fatherlinux:100000:65536 fred:165536:65536

También puede ver las asignaciones desde el interior de un contenedor:

 

Table 3: More on users inside and outside the container

Tenga en cuenta que, cuando Podman se ejecuta como superusuario, el intervalo completo de ID de usuario está disponible en el contenedor (4294967295 == 32 bits). Pero, cuando Podman se ejecuta como fatherlinux, asigna el superusuario dentro del contenedor al usuario fatherlinux (1000), y el usuario sync (uid 5) a un UID en el intervalo entre 100 000 y 165 535. 

Esta es una gran característica de seguridad porque, en ese caso, el motor del contenedor y el proceso dentro del contenedor en ejecución se ejecutan como usuarios diferentes y sin privilegios. Los ID de usuario en el intervalo de 100 000 a 165 535 no cuentan con privilegios especiales en el sistema, ni siquiera como el usuario fatherlinux (1000). Esto implica que, si un proceso se separara del contenedor, se lo restringiría enormemente en el host del contenedor. 

Otra pregunta que surge es si el sistema puede quedarse sin UIDs cuando se agregan muchos usuarios. La respuesta corta es sí. Pero esto es poco probable, dado que los UID están representados por un número 32 con 4000 millones de UID. Esto quiere decir que puede agregar hasta 65 535 usuarios a un sistema (4294967295 dividido por 65535). Esa cantidad debería ser suficiente para la mayoría de los casos prácticos.

Ahora, profundicemos en un último aspecto de los contenedores sin privilegios de superusuario. Para asignar el usuario dentro del contenedor a un usuario fuera de él se utiliza el archivo /etc/subuid, pero, para ello, antes debe definir el usuario (fatherlinux en el siguiente ejemplo) en la imagen del contenedor o Podman no podrá iniciarlo:

podman run --user fatherlinux -it ubi8 bash

Resultado:

No se puede encontrar al usuario fatherlinux: no hay entradas que coincidan en el archivo passwd

El ID de usuario que especifique debe encontrarse tanto en el contenedor como en el archivo /etc/passwd dentro de la imagen del contenedor. Este es otro ejemplo más de cómo los contenedores están ligados intrínsecamente al sistema operativo del contenedor y separados del de los hosts. Los contenedores son Linux.

La protección de los contenedores: un análisis profundo

Este concepto no es fácil de entender con el daemon Docker debido al modelo cliente-servidor. Con este modelo docker, podemos ejecutar un contenedor como superusuario incluso si ejecutamos el comando como un usuario común. Esto se debe a que el daemon docker se ejecuta como superusuario, y dispone de todos los privilegios correspondientes. Espero que esta aclaración haya servido para entender mejor el tema. Para demostrarlo, ejecute los siguientes comandos:

 

Table 4: Commands to check user IDs

Para asegurarse de que el usuario que ejecuta el contenedor no obtenga acceso de superusuario a su host, necesita ejecutar el motor del contenedor y el proceso dentro de este como un usuario común. Al hacerlo, se obtienen varias capas de seguridad entre el servicio (httpd, MySQL, y otros) y los recursos del sistema operativo que requieren privilegios. Si ejecuta el motor del contenedor como un usuario común, obtiene una capa de protección. Si, además, ejecuta el proceso en el contenedor como un usuario común diferente, obtiene otra capa. 

Dan Walsh explora el tema con más profundidad en este excelente artículo: Running Rootless Podman as a  non-root User. En términos generales, un motor de contenedores sin privilegios de superusuario como Podman puede ejecutarse con su cuenta de usuario. Luego, dentro del contenedor, puede usar un conjunto virtual de usuarios que se asignan a un conjunto de ID de usuario controlados solo por su cuenta para los procesos en contenedores. 

Ahora debería poder comprender mejor las propiedades de los superusuarios dentro y fuera de un contenedor.


About the author

At Red Hat, Scott McCarty is Senior Principal Product Manager for RHEL Server, arguably the largest open source software business in the world. Focus areas include cloud, containers, workload expansion, and automation. Working closely with customers, partners, engineering teams, sales, marketing, other product teams, and even in the community, he combines personal experience with customer and partner feedback to enhance and tailor strategic capabilities in Red Hat Enterprise Linux.

McCarty is a social media start-up veteran, an e-commerce old timer, and a weathered government research technologist, with experience across a variety of companies and organizations, from seven person startups to 20,000 employee technology companies. This has culminated in a unique perspective on open source software development, delivery, and maintenance.

Read full bio