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.
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
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:
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:
- Instalar aplicaciones de Flatpak y observar qué sucede cuando más de un proceso está involucrado.
- Examinar qué ocurre con las tablas de ruteo, las listas de iptables o nftables, etc., en un nuevo namespace de red.
- 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
- man7.org - namespaces
- man1.org - unshare)
- man1.org - nsenter)
-
Docker - Namespaces Overview
- Linux-native "fake root" for implementing rootless containers