Introducción a los contenedores rootless con Podman
Los contenedores Podman tienen dos modos de ejecución:
- Contenedor rootful: es un contenedor ejecutado por el usuario
root
en el host. Por lo tanto, tiene acceso a toda la funcionalidad que el usuarioroot
tiene.- Este modo de funcionamiento puede tener problemas de seguridad, ya que si hay una vulnerabilidad en la funcionalidad, el usuario del contenedor será
root
con los posibles riesgos de seguridad que esto conlleva. - De todas maneras, es posible que algunos procesos ejecutados en el contenedor no se ejecuten como
root
.
- Este modo de funcionamiento puede tener problemas de seguridad, ya que si hay una vulnerabilidad en la funcionalidad, el usuario del contenedor será
- Contenedor rootless: es un contenedor que puede ejecutarse sin privilegios de
root
en el host.- Podman no utiliza ningún demonio y no necesita
root
para ejecutar contenedores. - Esto no significa que el usuario dentro del contenedor no sea
root
, aunque sea el usuario por defecto. - Si tenemos una vulnerabilidad en la ejecución del contenedor, el atacante no obtendrá privilegios de
root
en el host.
- Podman no utiliza ningún demonio y no necesita
En este artículo vamos a trabajar con contenedores rootless, y lo primer que vamos a indicar son las limitaciones que tenemos al trabajr con este tipo de contenedores:
- No tienen acceso a todas las características del sistema operativo.
- No se pueden crear contenedores que se unan a puertos privilegiados (menores que 1024).
- Algunos modos de almacenamiento pueden dar problemas.
- Por defecto, no se puede hacer
ping
a servidores remotos. - No pueden gestionar las redes del host.
- Más limitaciones
Podemos crear un contenedor rootless con un usuario sin privilegios de manera similar a cómo lo haríamos con el usuario root
. Tenemos en cuenta que no podemos utilizar los puertos privilegiados (menores del 1024), por lo que en este caso hemos mapeado el puerto 8080:
$ podman run -d --name my-apache-app -p 8080:80 docker.io/httpd:2.4
Cada usuario sin privilegios tiene sus imágenes en su registro local:
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/httpd 2.4 67c2fc9e3d84 2 weeks ago 151 MB
Podemos ver el contenedor que tenemos en ejecución:
$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cccf1c02229e docker.io/library/httpd:2.4 httpd-foreground About a minute ago Up About a minute 0.0.0.0:8080->80/tcp my-apache-app
Y podemos acceder a la dirección IP del host y al puerto que hemos mapeado para acceder al host:
Modos de funcionamiento de los contenedores rootless
Cuando ejecutamos un contenedor rootful o rootless con Podman, los procesos que se ejecutan dentro del contenedor pueden estar ejecutados por el usuario root
o por un un usuario sin privilegios. Nos vamos a centrar en este apartado en los dos modos de funcionamiento de los contenedores rootless.
Para hacer estos ejemplos vamos a usar el usuario usuario
con UID 1000 para crear los contenedores.
Ejecución de contenedores rootless, con procesos en el contenedor ejecutándose como root
Veamos un ejemplo:
$ id
uid=1000(usuario) gid=1000(usuario) groups=1000(usuario),4(adm),10(wheel),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ podman run -d --rm --name contenedor1 alpine sleep 1000
f3961860f97280adf64c44a8b42dd39588712d3935469bf97d3ae7d71b8ffa97
$ podman exec contenedor1 id
uid=0(root) gid=0(root)
$ ps -ef | grep sleep
usuario 23234 23232 0 09:47 ? 00:00:00 sleep 1000
$ podman top contenedor1 huser user
HUSER USER
1000 root
- En primer lugar vemos que el usuario que va a crear el contenedor es
usuario
. - Comprobamos que el usuario que está ejecutando los procesos dentro del contenedor es
root
. - Comprobamos que en el host, el proceso lo ejecuta el usuario
usuario
. - Por último, vemos que el usuario correspondiente al host (
HUSER
) esusuario
(UID 1000), y el usuario dentro del contenedor (USER
) esroot
. Hay una correspondencia entre nuestro usuario sin privilegios en el host y el usuarioroot
dentro del contenedor.
Podemos concluir: cuando ejecutamos un contenedor con un usuario sin privilegios, con el proceso del contenedor ejecutándose con root
, el usuario real visible en el host que ejecuta el proceso es el usuario sin privilegios con su UID.
Ejecución de contenedores rootful, con procesos en el contenedor ejecutándose con usuarios no privilegiados
En el caso de los contenedores rootless, también podemos indicar el usuario que ejecutara los procesos dentro del contenedor con el parámetro --user
o -u
, o utilizando una imagen donde venga definido. Veamos un ejemplo:
$ id
uid=1000(usuario) gid=1000(usuario) groups=1000(usuario),4(adm),10(wheel),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ podman run -d --rm -u sync --name contenedor1 alpine sleep 1000
96d64bf75b7998de86624dd699f450f83670b4a798e775585edc8c2607de94ca
$ podman exec contenedor1 id
uid=5(sync) gid=0(root)
$ ps -ef | grep sleep
524292 23377 23375 0 09:57 ? 00:00:00 sleep 1000
$ podman top contenedor1 huser user
HUSER USER
524292 sync
En este caso:
- En primer lugar vemos que el usuario que va a crear el contenedor es
usuario
. - Comprobamos que el usuario que está ejecutando los procesos dentro del contenedor es
sync
. - Comprobamos que en el host, el proceso lo ejecuta un usuario con UID 524292.
- Por último, vemos que el usuario correspondiente al host (
HUSER
)es un usuario con UID 524292, y el usuario dentro del contenedor (USER
) essync
. Hay una correspondencia entre un usuario sin privilegios en el host y el usuariosync
dentro del contenedor.
Podemos concluir: cuando ejecutamos un contenedor con un usuario sin privilegios, con el proceso del contenedor ejecutándose con un usuario sin privilegios, el usuario real visible en el host que ejecuta el proceso es otro usuario sin privilegios con un UID propio.
Espacio de nombres de usuario
Los espacios de nombres (namespaces) son un mecanismo que el kernel de Linux utiliza para aislar y restringir recursos del sistema operativo, como procesos, redes, sistemas de archivos, entre otros. Los namespaces permiten crear entornos de ejecución independientes
Cuando ejecutamos contenedores rootless, hemos visto que podemos ejecutar los procesos dentro del contenedor con otros usuarios. Además dentro de la imagen que estamos usando para crear el contenedor pueden estar definidos varios usuarios. Sin embargo, el kernel de Linux impide a un usuario sin privilegios usar más de un UID, por ello necesitamos un mecanismo que consiga que nuestro usuario sin privilegio pueda utilizar distintos UID y GID.
Es por todo ello, que se use un espacio de nombre de usuario (user username):
- Nos permite asignar un rango de IDs de usuario y grupo en un espacio de nombres aislado. Esto significa que los procesos que se ejecutan dentro de ese namespace tienen una visión limitada de los usuarios y grupos del sistema en comparación con el sistema anfitrión.
- Nos permite establecer una correspondencia entre los ID de usuario del contenedor y los ID de usuario del host.
- En el espacio de nombres de usuario de Podman, hay un nuevo conjunto de IDs de usuario e IDs de grupo, que están separados de los UIDs y GIDs de su host.
Por ejemplo, como vimos en los ejemplos anteriores:
- Cuando creamos un contenedor rootless donde se ejecutan los procesos como
root
(uid = 0), en el host se están ejecutando con el usuario que ha creado el contenedor, en nuestro caso con el usuariousuario
(uid = 1000). - Cuando creamos un contenedor rootless donde se ejecutan los procesos con el usuario
sync
(uid = 5), en el host se están ejecutando con un usuario sin privilegios con uid = 524292.
Cada usuario no privilegiado que creemos en nuestro host, tendrá un conjunto de UID y GID que podrá mapear a usuarios y grupos dentro del contenedor:
- En el fichero
/etc/subuid
, por cada usuario tenemos el UID inicial y la cantidad de identificadores que puede mapear. Cada usuario tiene que tener un conjunto de identificadores diferentes.$ cat /etc/subuid usuario:524288:65536
El usuario
usuario
puede mapear desde el UID 524288 y tiene asignado 65536 identificadores. - En el fichero
/etc/subgid
está definido, con el mismo formato los identificadores de grupos que puede mapear cad usuario.
Podemos ver el mapeo de identificadores de usuario que se ha realizado leyendo el fichero /proc/self/uid_amp
en el contenedor. Si ejecutamos la siguiente instrucción en el último ejemplo que hemos presentado (contenedor rootless cuyos procesos se ejecuta por el usuario sync
):
$ podman exec contenedor1 cat /proc/self/uid_map
0 1000 1
1 524288 65536
El mapeo que se ha realizado es el siguiente:
- El usuario
root
(UID = 0) está mapeado con el usuariousuario
(UID = 1000) para un rango de 1. - Luego el UID 1 está mapeado al UID 524288 para un rango de 65536 UIDS. Por eso el usuario
sync
con UID = 5, se mapea al UID = 524292.
Desde el punto de vista de la seguridad es un aspecto muy positivo, ya que la ejecución del contenedor y de los procesos dentro del contenedor se hace por usuarios diferentes y sin privilegios. El rango de IDs que mapea un usuario, no tiene ningún privilegios especial en el sistema, ni siquiera como el usuario usuario
(UID = 1000). Esto significa que si un proceso en el contenedor tiene un problema de seguridad estará restringido en el host del contenedor.
Por lo tanto el uso de contenedores rootless aumenta la seguridad consiguiendo un aislamiento de privilegios, ya que al ejecutar contenedores en modo rootless, el usuario no necesita privilegios de superusuario para iniciar y administrar los contenedores. Además como se usan un conjunto de IDs de usuario y grupo dentro del contenedor que son diferentes de los IDs en el sistema anfitrión, nos proporciona una capa adicional de aislamiento de seguridad, ya que los procesos dentro del contenedor no tienen privilegios en el sistema anfitrión. Dicho de otro modo, se reduce el riesgo de que un contenedor comprometido pueda acceder o modificar recursos críticos del sistema anfitrión.
podman unshare
La instrucción podman unshare
, nos permite entrar en un espacio de nombres de usuario sin lanzar un contenedor. Le permite examinar lo que está sucediendo dentro del espacio de nombres de usuario, cambiado el conjuntos de IDs que se está usando para identificar a los usuarios y los grupos.
Por ejemplo, si ejecutamos la siguiente instrucción en el host:
$ cat /proc/self/uid_map
0 0 4294967295
Se nos muestra el mapeo en los contenedores roorful, el ID 0 se mapea con el 0, y así sucesivamente con todos el rango de identificadores.
Sin embargo, podemos entrar en el espacio de nombre de usuario y ejecutar esa misma instrucción:
$ podman unshare cat /proc/self/uid_map
0 1000 1
1 524288 65536
Obteniendo el mismo resultado que al ejecutarla dentro del contenedor.
Leave a Comment
Your email address will not be published. Required fields are marked *