Qué son los namespaces y que podemos hacer con ellos

En el post anterior, exploramos qué es Flatpak, una excelente alternativa o complemento a los métodos tradicionales de instalación de programas en Linux. Flatpak utiliza una herramienta llamada bubblewrap, que a su vez emplea la tecnología de namespaces.

Namespaces en Linux

Veamos con lupa lo que hay debajo de una aplicación flatpak.

Listado de procesos de aplicaciones flatpak

 flatpak ps --columns=pid,child-pid,application
PID  PID-hijo Aplicación
4588 4610     com.github.marktext.marktext
4629 4639     com.github.marktext.marktext
4841 4853     com.spotify.Client

MarkText y Spotify (aplicaciones instaladas con flatpak)

Este listado nos muestra que hay dos aplicaciones corriendo: MarkText y Spotify. Vamos a ver qué pasa con uno de los procesos relacionados con MarkText.

El comando detrás de flatpak

 ps p  4610 o args
COMMAND
/usr/bin/bwrap --args 42 -- spotify

bwrap es una herramienta para crear sandboxes por medio de namespaces. Un namespace es un recurso del sistema que se puede aislar, pero... en lugar de abundar en tecnicismos, para comprender este concepto en la práctica, veremos algunas propiedades del proceso 4610 (MarkText):

 ls -l /proc/4610/ns
total 0
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:40 mnt -> 'mnt:[4026532856]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 net -> 'net:[4026531840]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:40 pid -> 'pid:[4026532857]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 pid_for_children -> 'pid:[4026532857]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 time -> 'time:[4026531834]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:40 user -> 'user:[4026532858]'
lrwxrwxrwx 1 sergio sergio 0 jul 31 18:46 uts -> 'uts:[4026531838]'

Ese listado nos muestra que están los namespaces cgroup, ipc, mnt, net, pid, time, user y uts. Cada uno de ellos tiene un número que lo identifica.

Podemos compararlo con otros procesos por ejemplo con el pid 1 y con el pid del shell que estamos usando:

Comparación de procesos y namespaces

Podemos ver que tanto bash como systemd comparten todos los namespaces. Sin embargo, bwrap tiene namespaces distintos para mnt, pid y user. Es decir tiene esos recursos aislados.

El cuadro siguiente nos sirve para saber qué namespace usar de acuerdo a lo que queramos aislar:

Para aislar Usar namespace...
Límites de recursos cgroups
Colas de mensaje, semáforos, memoria compartida ipc
Lista de montajes mount
Interfaces de red, ruteo, sockets, etc. net
Números de pid pid
UID's, GID's, capabilities, etc user
Hostnames uts
Relojes/Tiempo time

El soporte en el kernel lo podemos verificar de la siguiente manera:

grep -E 'CONFIG_[A-Z]+_NS=y' /boot/config-$(uname -r)
CONFIG_UTS_NS=y
CONFIG_TIME_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y

Crear nuevos namespaces para procesos y usuarios

La herramienta unshare nos permite experimentar y solucionar problemas en aplicaciones y servicios que usan namespaces. En el siguiente ejemplo abrimos un shell con namespaces aislados para usuarios y números de procesos:

 unshare --mount-proc --pid --fork --user bash
❯ ps aux
   USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
   nobody         1  2.8  0.0 237808 12996 pts/1    S    13:43   0:00 bash
   nobody       104  0.0  0.0 230836  4096 pts/1    R+   13:43   0:00 ps aux
  • Aquí vemos que bash usa el PID 1 en lugar de systemd, y que el comando ps aux toma el pid 2.
  • Además, el usuario pasa a ser nobody.
  • Para salir del namespace, simplemente hay que ejecutar exit. Es importante asegurarse de que todos los procesos dentro del namespace hayan terminado, de lo contrario, puede ser que exit sea insuficiente para salir completamente del namespace.

¿Por qué usamos --mount-proc?

Bueno... porque si no lo hacemos sucede esto:

 unshare --mount --pid --fork --user /bin/bash
basename: falta un operando
Pruebe 'basename --help' para más información.

Este mensaje aparece porque el comando basename usa readlink para ver hacia dónde apunta /proc/$$/exe. Como el PID de la shell en el nuevo namespace es 1, intenta acceder incorrectamente al directorio /proc en los namespaces del host. Linux impide este acceso para mantener el aislamiento y la seguridad, lo que resulta en ese error.

Particularidades de namespaces

 unshare --mount-proc --pid --user /bin/bash
unshare: mount /proc failed: Operación no permitida
❯ unshare --pid --user /bin/bash
bash: fork: No se pudo asignar memoria

En el primer intento, ni siquiera pudo crear los namespaces; en el segundo, aunque lo logró, muestra un mensaje críptico:

bash: fork: No se pudo asignar memoria

Estos errores ocurren porque unshare, por defecto, ejecuta el comando indicado en el mismo proceso (similar a exec), lo que resulta en:

  • Error de Montaje: El sistema no permite montar /proc porque el proceso no tiene el contexto completo de namespace de usuario y PID necesario.
  • Error de Memoria: Debido a que bash no se convierte en PID 1 ni puede acceder a otro proceso con PID 1, Linux no permite la asignación de memoria porque no hay un proceso para manejar la recolección de zombies y otras tareas esenciales.

Por lo tanto, usar --fork es necesario para asegurar que el proceso tenga el contexto de namespace completo y para crear un nuevo proceso que actúe como PID 1 en el nuevo namespace.

Entrar en namespaces de una app de flatpak

 sudo nsenter --mount --pid -S $UID -t 4610
-bash-5.2$ ps auxwww
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sergio         1  0.0  0.0   3764  1288 ?        S    18:40   0:00 /usr/bin/bwrap --args 40 -- marktext
sergio         2  0.9  0.5 21637908 182648 ?     SLl  18:40   0:15 /app/marktext/marktext
sergio         5  0.0  0.0   5640  1664 ?        S    18:40   0:00 cat
sergio         6  0.0  0.0   5640  1792 ?        S    18:40   0:00 cat
sergio        10  0.0  0.1 16987244 46720 ?      S    18:40   0:00 /app/marktext/marktext --type=zygote --no-zygote-sandbox
sergio        12  0.0  0.0      0     0 ?        Z    18:40   0:00 [zypak-sandbox] <defunct>
sergio        15  0.0  0.0   3768  1152 ?        S    18:40   0:00 /usr/bin/bwrap --args 42 -- /app/bin/zypak-helper child - /app/marktext/marktext --type=zygote
sergio        16  0.0  0.1 16989892 49152 ?      S    18:40   0:00 /app/marktext/marktext --type=zygote
sergio        48  1.4  0.2 17065152 72664 ?      Sl   18:40   0:23 /app/marktext/marktext --type=gpu-process --field-trial-handle=9170851604328465342,17458150017059513700,131072 --disable-features=SpareRendererForSitePerProcess --enable-crash-reporter=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel --global-crash-keys=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel,_companyName=marktext,_productName=marktext,_version=0.17.1 --user-data-dir=/home/sergio/.var/app/com.github.marktext.marktext/config/marktext --gpu-preferences=UAAAAAAAAAAgAAAIAAAAAAAAAAAAAAAAAABgAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAA= --use-gl=swiftshader-webgl --shared-files
sergio        53  0.0  0.0 16998948 14448 ?      S    18:40   0:00 /app/marktext/marktext --type=broker
sergio        63  0.0  0.1 17044740 57216 ?      Sl   18:40   0:00 /app/marktext/marktext --type=utility --utility-sub-type=network.mojom.NetworkService --field-trial-handle=9170851604328465342,17458150017059513700,131072 --disable-features=SpareRendererForSitePerProcess --lang=es-419 --service-sandbox-type=none --enable-crash-reporter=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel --global-crash-keys=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel,_companyName=marktext,_productName=marktext,_version=0.17.1 --user-data-dir=/home/sergio/.var/app/com.github.marktext.marktext/config/marktext --shared-files=v8_context_snapshot_data:100
sergio        74  5.2  0.6 25595728 212480 ?     Sl   18:40   1:27 /app/marktext/marktext --type=renderer --enable-crash-reporter=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel --global-crash-keys=7744fdfa-fe51-4b1f-adf6-daefea565c51,no_channel,_companyName=marktext,_productName=marktext,_version=0.17.1 --user-data-dir=/home/sergio/.var/app/com.github.marktext.marktext/config/marktext --app-path=/app/marktext/resources/app.asar --no-sandbox --no-zygote --field-trial-handle=9170851604328465342,17458150017059513700,131072 --disable-features=SpareRendererForSitePerProcess --disable-gpu-compositing --lang=es-419 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=4 --no-v8-untrusted-code-mitigations --shared-files=v8_context_snapshot_data:100
sergio       144  0.0  0.0   7884  4224 ?        S    19:08   0:00 -bash
sergio       145  0.0  0.0  11032  4608 ?        R+   19:08   0:00 ps auxwww

La herramienta nsenter permite ejecutar comandos en namespaces de un proceso determinado, en este caso, el de bwrap. Como no especificamos ningún comando, corre el shell bash.

Allí podemos ver el espacio de nombres aislados de números de procesos, como se ve arriba, o como mostramos a continuación, los montajes aislados:

-bash-5.2$ df -h  
S.ficheros     Tamaño Usados  Disp Uso% Montado en  
tmpfs             16G    92K   16G   1% /etc/timezone  
/dev/sda6        511G   434G   76G  86% /cs  
tmpfs             16G      0   16G   0% /usr/lib/x86_64-linux-gnu/GL  
/dev/sda6        511G   434G   76G  86% /home  
tmpfs            3,2G    11M  3,2G   1% /run/host/monitor  
tmpfs             16G    15M   16G   1% /dev  
devtmpfs         4,0M      0  4,0M   0% /dev/tty  
tmpfs             16G      0   16G   0% /home/sergio/.local/share/flatpak  
tmpfs             16G      0   16G   0% /home/sergio/.var/app  
/dev/sda3        382G   100G  283G  27% /mnt/win10  
tmpfs            6,3G   2,3M  6,3G   1% /run/media  
tmpfs             16G   4,0M   16G   1% /tmp  
tmpfs             16G      0   16G   0% /tmp/.X11-unix  

También podemos pensar que estos recursos que se aíslan se virtualizan. Cuando hablamos de virtualización, probablemente pensemos en un disco o en una interfaz de red virtual. Pero aquí vemos, por ejemplo, que un espacio de nombres de PID's también se puede virtualizar.

Y cuando más de un recurso se puede virtualizar, de ahí a crear un contenedor hay una corta distancia. De hecho, los namespaces sirve tanto para un usuario final con flatpak como en la creación de contenedores lxc (ah, sí lxc existe desde 2008, aunque ya no esté más de moda 😁) docker o podman para un sysadmin o un desarrollador.

A continuación, veremos algunos ejemplos de cómo utilizar los namespaces en contenedores Podman. Es importante conocer el PID del contenedor en el host para estos ejemplos.

Ejemplo 1: Ver la configuración de red del contenedor que usa network mode pasta

 podman unshare nsenter --target 29420 --net   ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host proto kernel_lo 
       valid_lft forever preferred_lft forever
2: wlp108s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether ea:38:5b:0b:a8:a8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.144/24 brd 192.168.0.255 scope global noprefixroute wlp108s0
       valid_lft forever preferred_lft forever
    inet6 fe80::e838:5bff:fe0b:a8a8/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever

Ejemplo 2: Ver la configuración de red del contenedor que usa network mode slirp4netns

  podman unshare nsenter --target 46222 --net  

  root in ~/to_delete 
   ip a
  1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
      inet 127.0.0.1/8 scope host lo
         valid_lft forever preferred_lft forever
      inet6 ::1/128 scope host proto kernel_lo 
         valid_lft forever preferred_lft forever
  2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
      link/ether 92:b6:67:25:13:33 brd ff:ff:ff:ff:ff:ff
      inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
         valid_lft forever preferred_lft forever
      inet6 fd00::90b6:67ff:fe25:1333/64 scope global dynamic mngtmpaddr proto kernel_ra 
         valid_lft 86356sec preferred_lft 14356sec
      inet6 fe80::90b6:67ff:fe25:1333/64 scope link proto kernel_ll 
         valid_lft forever preferred_lft forever

Ejemplo 3: Probar la resolución de nombres usando el archivo /etc/resolv.conf del contenedor

 container_id=$(podman inspect --format '{{.Id}}' namespace-demo) cp /run/user/1000/containers/overlay-containers/$container_id/userdata/resolv.conf /tmp/container_resolv.conf     podman unshare nsenter --target 46222  --net dig google.com @$(grep -m 1 'nameserver' /tmp/container_resolv.conf | awk '{print $2}')

; <<>> DiG 9.18.26 <<>> google.com @10.0.2.3
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25213
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;google.com.                    IN      A

;; ANSWER SECTION:
google.com.             263     IN      A       142.251.133.78

;; Query time: 30 msec
;; SERVER: 10.0.2.3#53(10.0.2.3) (UDP)
;; WHEN: Thu Jul 25 19:50:18 -03 2024
;; MSG SIZE  rcvd: 55

Ejemplo 4: Ver los procesos de un contenedor rootless

 sudo nsenter --pid=/proc/$container_pid/ns/pid unshare --mount-proc
[sudo] contraseña para sergio:  ps
  PID TTY          TIME CMD
 1508 pts/6    00:00:00 bash
 1612 pts/6    00:00:00 ps
❯ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sergio         1  0.0  0.0  14932  7168 ?        Ss   jul25   0:00 sudo /usr/sbin/sshd -D
sergio         2  0.0  0.0  14084  7936 ?        S    jul25   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1508  0.5  0.0 231260  6528 pts/6    S    19:00   0:00 -bash
root        1641  0.0  0.0 230836  3968 pts/6    R+   19:00   0:00 ps aux

Ejemplo 5: Crear namespaces no privilegiados con un poco de ayuda

Por último, veamos como usar la herramienta rootlesskit para crear fácilmente namespaces rootless:

 rootlesskit bash
❯ id
uid=0(root) gid=0(root) grupos=0(root),65534(nobody) exit rootlesskit --copy-up=/etc bash   touch /etc/sample
❯ ls -l /etc/sample
-rw-r--r-- 1 root root 0 jul 26 12:12 /etc/sample
❯ exit ls -l /etc/sample
ls: no se puede acceder a '/etc/sample': No existe el fichero o el directorio

Conclusión

En este post, hemos visto que los namespaces sirven para aislar recursos con diferentes propósitos y que herramientas como unshare y nsenter son muy útiles para entender cómo funciona una aplicación instalada con Flatpak, pero también un contenedor podman o similar, lo cual facilita la resolución de problemas. Algunas actividades que propongo para continuar ejercitando este tema son:

  1. Instalar aplicaciones de Flatpak y observar qué sucede cuando más de un proceso está involucrado.
  2. Examinar qué ocurre con las tablas de ruteo, las listas de iptables o nftables, etc., en un nuevo namespace de red.
  3. Crear un namespace de usuario y explorar cómo este afecta la ejecución de comandos y procesos.

¡Hasta la próxima!

Fuentes y Recursos

  1. man7.org - namespaces
  2. man1.org - unshare)
  3. man1.org - nsenter)
  4. Docker - Namespaces Overview
  5. Linux-native "fake root" for implementing rootless containers

Comentarios

Comments powered by Disqus