Qué son los cgroups y para qué sirven

En el post anterior vimos los namespaces, que sirven para aislar diferentes aspectos del sistema, como los procesos, la red y los puntos de montaje, creando entornos independientes para cada grupo de procesos. En este post, exploraremos los cgroups, que permiten controlar cuánto de los recursos del sistema, como CPU, memoria y disco, puede usar cada grupo de procesos. Ambas tecnologías se complementan: los namespaces proporcionan el aislamiento necesario, mientras que los cgroups gestionan y limitan los recursos que esos entornos aislados pueden consumir.

Namespaces en Linux

¿Qué son los cgroups?

Los cgroups (control groups) son una funcionalidad del kernel Linux que permite agrupar procesos y controlar de manera granular los recursos del sistema que consumen, como CPU, memoria, I/O, y otros. Se incorporaron en 2008 en el kernel de Linux 2.6.24, los cgroups permiten a los administradores de sistemas gestionar el uso de recursos por grupos de procesos de forma eficiente, garantizando una distribución justa y controlada.

Los cgroups crean una jerarquía de control donde es posible asignar límites a recursos específicos para un conjunto de procesos, de manera que el sistema operativo pueda monitorear, limitar, priorizar o aislar estos procesos según sea necesario. Esta capacidad es clave para garantizar que los sistemas multitarea, servidores, y contenedores funcionen de manera eficiente.

¿Para qué sirven los cgroups?

Los cgroups sirven para:

  • Limitar el uso de recursos

  • Priorizar procesos

  • Aislar procesos

  • Monitorear el consumo de recursos

  • Controlar procesos en contenedores (Docker, podman,etc.)

Un pantallazo de los cgroups

Dentro del propio directorio /proc podemos encontrar información sobre los cgroups:

 cat /proc/cgroups 
#subsys_name    hierarchy       num_cgroups     enabled
cpuset  0       241     1
cpu     0       241     1
cpuacct 0       241     1
blkio   0       241     1
memory  0       241     1
devices 0       241     1
freezer 0       241     1
net_cls 0       241     1
perf_event      0       241     1
net_prio        0       241     1
hugetlb 0       241     1
pids    0       241     1
rdma    0       241     1
misc    0       241     1

Este archivo muestra 4 columnas

Campo Significado
subsys_name Componente del kernel que controlar un recurso específico un aspecto del sistema.
hierarchy La columna hierarchy indica el ID de jerarquía asignado a cada subsistema. En este caso, todas las jerarquías tienen el valor 0, lo que indica que todos los subsistemas listados están utilizando una jerarquía unificada, común en sistemas que usan cgroups v2. En cgroups v2, todos los controladores utilizan una única jerarquía para gestionar los recursos de manera más eficiente, a diferencia de cgroups v1, donde cada subsistema podía tener su propia jerarquía.
num_cgroups Muestra el número de cgroups activos o instancias creadas bajo cada controlador. Estos cgroups pueden pertenecer a procesos que están ejecutándose actualmente en el sistema, como servicios, contenedores, o procesos individuales.
enabled Esta columna muestra si el subsistema está habilitado o no. El valor 1 indica que el controlador está habilitado y activo, mientras que un valor de 0 indicaría que está deshabilitado.

Evolución de los cgroups:

  1. cgroups v1: En la versión original, cada tipo de recurso (CPU, memoria, etc.) tenía su propia jerarquía de control, lo que permitía una gestión separada pero a veces compleja. Aunque era útil, la administración independiente de subsistemas podía resultar difícil de manejar en entornos complejos.

  2. cgroups v2 (2016): Para mejorar la gestión de recursos, se introdujo cgroups v2 en el kernel de Linux 4.x, que unificó los diferentes controladores bajo una jerarquía única. Esta versión simplificó el control de recursos y mejoró la eficiencia, permitiendo que todos los subsistemas trabajaran de manera más coordinada y con configuraciones más claras.

Cómo crear un cgroup y limitar el uso de CPU en cgroups v2

En este ejemplo, limitaremos el uso de CPU de un proceso utilizando cgroups v2 en un entorno moderno.

Paso 1: Crear un directorio para el cgroup

En cgroups v2, se gestiona todo bajo una jerarquía unificada. Primero, crea un nuevo directorio en /sys/fs/cgroup:

sudo mkdir /sys/fs/cgroup/mi_cgroup
Paso 2: Establecer un límite de CPU

En cgroups v2, puedes establecer límites en el uso de CPU mediante el archivo cpu.max. Este archivo recibe dos valores: el primero es el límite de uso de CPU en microsegundos por cada período de 100 milisegundos, y el segundo es la duración del período en microsegundos.

Para limitar el uso de CPU al 50%, puedes hacer lo siguiente:

echo "50000 100000" | sudo tee /sys/fs/cgroup/mi_cgroup/cpu.max

Este comando establece que los procesos en este cgroup pueden usar un máximo de 50.000 microsegundos de CPU en un período de 100.000 microsegundos (100 ms), lo que equivale al 50% de un núcleo de CPU.

Paso 3: Añadir un proceso al cgroup

Para añadir un proceso existente al cgroup, escribe su PID (ID del proceso) en el archivo cgroup.procs de ese cgroup. Supongamos que el PID del proceso es 78435:

echo 78435| sudo tee /sys/fs/cgroup/mi_cgroup/cgroup.procs

Esto moverá el proceso con PID 78435 al cgroup mi_cgroup, aplicando las restricciones de CPU configuradas.

Ejemplo de uso de cgroups para limitar el uso de CPU

En cgroups v1, los recursos se gestionan usando archivos como cpu.shares para establecer prioridades relativas de CPU, y tasks para asignar procesos a un cgroup. En cambio, en cgroups v2, el control de CPU se realiza a través de cpu.max, donde se puede establecer un límite absoluto en el uso de CPU (como un porcentaje). Además, en v1 cada subsistema tiene su propio directorio (como cpu/, memory/), mientras que en v2 todo se gestiona dentro de una única jerarquía unificada bajo /sys/fs/cgroup/.

Conclusión

Hoy en día, los cgroups son una herramienta crítica en la gestión moderna de servidores. RHEL8 usa de manera predeterminada la versión 1, pero es posible usar la versión 2, dependiendo del software que utilicemos. En definitiva, los cgroups son fundamentales para la operación de tecnologías como contenedores (Docker, Podman), orquestadores como Kubernetes, y cualquier sistema que necesite control granular de recursos. La capacidad de aislar, priorizar y monitorear el uso de recursos en servidores es clave para garantizar la estabilidad y el rendimiento en entornos de alta demanda.

Fuentes y más información

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

¿Qué es flatpak?

Es conveniente y merecido que Flatpak tenga un post especialmente dedicado a él, más allá de su interacción con polkit que dejamos planteado en el post anterior.

Flatpak


Brevísimo repaso histórico

En agosto de 2007 el desarrollador sueco Alexander Larsson lanza su primer intento de empaquetar aplicaciones con todas sus dependencias. A fines de 2014 comienza a trabajar en el proyecto xdg-app, el cual cambia de nombre a Flatpak en 2016.

¿Qué es Flatpak?

Flatpak es un sistema para compilar, distribuir y ejecutar aplicaciones. Las aplicaciones se pueden compilar y distribuir independientemente del sistema en que se usan, y al ejecutarse están aisladas del mismo hasta cierto punto.

Cada aplicación está en un sandbox, de manera predeterminada solamente puede acceder al contenido del mismo. Cada programa en flatpak tiene acceso a

  • ~/.var/app/$FLATPAK_ID, y $XDG_RUNTIME_DIR/app/$FLATPAK_ID.
  • De manera limitada a llamadas del sistema.
  • De manera limitada a la instancia de D-BUS.

Aun así, para ser realistas, las aplicaciones en general necesitan más permisos, en este caso se insta a los empaquetadores a que adviertan qué accesos necesitará la aplicación para que el usuario esté conciente de ello, tal como cuando se instala una app desde el Play Store de Android.

Flatseal mostrando los permisos que usa cada una de las aplicaciones instaladas

Flatpak ofrece un repositorio llamado Flathub. Nada impide que cualquier persona o proyecto (como es el caso de Fedora) tenga el suyo propio.

Flatpak usa OSTree (Fedora usa OCI) para distribuir y desplegar datos. OSTree es similar a Git pero está diseñado para hacer control de versiones de binarios y archivos grandes de datos. El uso de OSTree permite además que un mismo archivo sea usado por más de una misma aplicación, ahorrando de esta manera espacio en disco. Cada aplicación que se instala es almacenada en un repositorio local de control de versiones, y luego se mapea en el sistema de archivos local.

Los cambios entre las diferentes versiones de una aplicación se aplican en el filesystem mediante enlaces duros.

Por ejemplo, con el comando siguiente podemos ver log del último commit de una aplicación flatpak (Logseq): .

 ostree log --repo=/var/lib/flatpak/repo  deploy/app/com.logseq.Logseq/x86_64/stable
commit a54ee70b0ec9047c2083f77b9f9175e8e8edcac998e3be8d395009255a6a03f6
Parent:  8c36feebe7ea23654917f542eb5419b71426359a42165f53496c9d82049a6806
ContentChecksum:  4d06f4e875d0335f2ff1c7a30ffa5b200c9a3eb3aeb0b608588eadb045994b22
Date:  2024-06-09 07:22:27 +0000

    Remove screenshot locale (8a160109)

    Name: com.logseq.Logseq
    Arch: x86_64
    Branch: stable
    Built with: Flatpak 1.14.8

<< History beyond this commit not fetched >>

En realidad el comando mostrado es de bajo nivel solamente a los efectos de mostrar ostree como software subyacente, para obtener información a más alto nivel, es conveniente usar directamente el comando flatpak para obtener información de la aplicación:

> flatpak info com.logseq.Logseq 

Logseq - Connect your notes and knowledge

                 ID: com.logseq.Logseq
         Referencia: app/com.logseq.Logseq/x86_64/stable
       Arquitectura: x86_64
               Rama: stable
            Versión: 0.10.9
           Licencia: AGPL-3.0-or-later
             Origen: flathub
          Colección: org.flathub.Stable
        Instalación: system
          Instalada: 437,7 MB
Tiempo de ejecución: org.freedesktop.Platform/x86_64/23.08
                Sdk: org.freedesktop.Sdk/x86_64/23.08

             Commit: a54ee70b0ec9047c2083f77b9f9175e8e8edcac998e3be8d395009255a6a03f6
              Padre: 8c36feebe7ea23654917f542eb5419b71426359a42165f53496c9d82049a6806
             Asunto: Remove screenshot locale (8a160109)
              Fecha: 2024-06-09 07:22:27 +0000

Comparación entre dnf, apt y flatpak

Flatpak está pensado para todo tipo de aplicaciones de escritorio y se esfuerza por ser tan neutral como sea posible en cuanto a los métodos que usa para compilar aplicaciones.

Además, flatpak proporciona actualizaciones atómicas, rollback y consistencia post-instalación.

El siguiente cuadro nos ayuda a ver las diferencias con los dos gestores populares de paquetes: dnf y apt.

Característica DNF APT Flatpak
Distribución Principal Fedora, RHEL, CentOS Debian, Ubuntu Varias distribuciones de Linux
Formato de Paquete RPM DEB OSTree, Flatpak Bundle
Gestión de Dependencias Manejo automático de dependencias Manejo automático de dependencias Manejo dentro del sandbox
Aislamiento No No Sí, aplicaciones ejecutadas en sandbox
Instalación de Aplicaciones Desde repositorios específicos del sistema Desde repositorios específicos del sistema Desde repositorios centralizados (Flathub) y repositorios personalizados
Actualizaciones dnf update apt update && apt upgrade flatpak update
Permisos de Instalación Requiere permisos de root Requiere permisos de root Puede instalarse sin permisos de root
Compatibilidad entre Distros Limitada a sistemas basados en RPM Limitada a sistemas basados en DEB Compatible con múltiples distribuciones
Seguridad Depende de la configuración del sistema Depende de la configuración del sistema Sandboxing y permisos controlados por portales
Facilidad de Uso Interfaz de línea de comandos Interfaz de línea de comandos Interfaz de línea de comandos y GUI
Espacio de Disco Compartido entre aplicaciones Compartido entre aplicaciones Deduplicación y compartición de runtimes
Formato de Archivo Individual No aplica No aplica Flatpak Bundle (.flatpak)
Desventajas Depende de la política de la distro Dependencia de la política de la distro Las aplicaciones pueden tener un mayor tamaño inicial
Compatibilidad con Aplicaciones Antiguas Sí, con soporte de versiones específicas Sí, con soporte de versiones específicas Generalmente más adecuado para nuevas aplicaciones
Desarrollo y Mantenimiento Desarrollado por el proyecto Fedora Desarrollado por el proyecto Debian Desarrollado por el proyecto Flatpak, con apoyo de Red Hat y GNOME
Adopción Usado por muchas distribuciones y entornos de escritorio Usado principalmente en Debian, Ubuntu y derivadas Usado en diversas distribuciones, especialmente para aplicaciones portátiles
Ejemplo de Comando de Instalación dnf install gedit apt install gedit flatpak install flathub org.gnome.Gedit

Inconvenientes para usuarios y desarrolladores de aplicaciones Linux

Si bien Linux mejoró significativamente en cuando a usabilidad y experiencia de usuario en los últimos años, hay obstáculos tanto para usuarios como para desarrolladores:

  • Una aplicación para un entorno de escritorio en Linux se debe empaquetar para que esté disponible (como Debian, Fedora, Ubuntu, Arch Linux, etc.), y asegurarse de que cumpla con los requisitos específicos de cada sistema de paquetes (deb, rpm, etc.). Esto requiere un esfuerzo considerable para mantener múltiples versiones y asegurarse de que cada paquete funcione correctamente en su respectiva distribución. Todo esto demanda o más tiempo o bien más gente idónea dedicada en la tarea de crear y mantener paquetes.

  • Si un usuario quiere usar aplicación que no está empaquetada en la distribución, tendrá que realizar algún paso extra, que van desde operaciones relativamente sencillas como bajar un ejecutable (darle permisos, ubicarlo en un directorio del PATH, etc.), hasta procedimientos bastante más complejos y fuera del interés y conocimiento de un usuario final como puede ser compilar el programa.

  • Una distribución de soporte a largo plazo como Debian Stable no posee versiones actualizadas de la mayoría de las aplicaciones. Por lo tanto un usuario tiene que elegir estabilidad a cambio de software un tanto anticuado.

Flatpak tiene como propósito justamente eliminar esas barreras. Gracias a Flatpak un usuario podrá instalar una aplicación como Logseq aun cuando no esté incluida en los repositorios de su distribución, o instalar una versión reciente de Kdenlive en Debian Stable.

En la actualidad existen distribuciones con la mirada puesta en la innovación que se benefician de Flatpak, por ejemplo:

Características de flatpak

Analicemos la app ONLYOFFICE en flatpak:

flatpak info org.onlyoffice.desktopeditors

ONLYOFFICE Desktop Editors - Office productivity suite

                 ID: org.onlyoffice.desktopeditors
         Referencia: app/org.onlyoffice.desktopeditors/x86_64/stable
       Arquitectura: x86_64
               Rama: stable
            Versión: 8.0.1
           Licencia: AGPL-3.0-only
             Origen: flathub
          Colección: org.flathub.Stable
        Instalación: system
          Instalada: 863,8 MB
Tiempo de ejecución: org.freedesktop.Platform/x86_64/23.08
                Sdk: org.freedesktop.Sdk/x86_64/23.08

             Commit: c9f73db43c9639df65e597108eb4c685025fe484fde05f73f650d05f9ea28512
              Padre: a38db7bbfaa18eda6d26e9d113ce850c077bf533d9ebf5aa7e34988a58b91b28
             Asunto: Update version to 8.0.1 (#117) (a88ad400)
              Fecha: 2024-03-04 15:34:56 +0000

La aplicación ONLYOFFICE usa como runtime org.freedesktop.Platform/x86_64/23.08. El runtime org.freedesktop.Platform/x86_64/23.08 es un entorno estandarizado y versionado que provee FreeDesktop.org para ejecutar aplicaciones de escritorio en Linux. Incluye una colección de librerías y servicios comunes, que aseguran la compatibilidad y la estabilidad entre las diferentes distribuciones de Linux. Este runtime está compilado específicamente para arquitecturas de 64-bit y fue actualizado o liberado en agosto de 2023.

Los runtimes no dependen ni de una distribución ni de una versión en particular de una distribución. Y una aplicación instalada en flatpak será exactamente igual no importa si se instala en Debian o en Fedora. Una aplicación como GIMP, empaquetada como Flatpak, debería proporcionar la misma funcionalidad principal y experiencia de usuario tanto en Debian como en Fedora, dado que todas las dependencias y configuraciones críticas se gestionan dentro del contenedor Flatpak. Desde luego, hay diferencias potenciales: Si una aplicación Flatpak utiliza características específicas del sistema, como integraciones con servicios de notificación de escritorio, la forma en que estas integraciones funcionan puede variar ligeramente entre GNOME en Fedora y KDE en Debian.

Existen diferentes runtimes para Flatpak, tales como freedesktop, GNOME, KDE y Elementary

Objeciones en cuanto a la seguridad

Han existido críticas severas a la seguridad de flatpak, en especial por el sitio anónimo Flatkill. El siguiente cuaddro es un resumen de los argumentos en contra y favor de Flatpak en cuanto a este tema:

Objeción de Seguridad Descripción Respuesta / Acción Tomada
Sandboxing Ineficaz Muchas aplicaciones en Flathub tienen permisos amplios como filesystem=host o filesystem=home, lo que permite acceso completo al sistema de archivos del usuario. Flatpak ha mejorado la transparencia sobre los permisos de las aplicaciones y trabaja en fortalecer el modelo de sandboxing. Se han introducido portales para permitir el acceso controlado a los recursos del sistema. Flatpak Documentation
Actualizaciones de Seguridad Retrasadas Algunas aplicaciones y runtimes en Flatpak no reciben actualizaciones de seguridad oportunas, lo que deja a los usuarios expuestos a vulnerabilidades conocidas. La herramienta flatpak-external-data-checker automatiza la verificación de fuentes externas y la generación de solicitudes de fusión cuando se encuentran actualizaciones. Al implementar f-e-d-c, los desarrolladores pueden asegurar que las aplicaciones en Flathub se mantengan actualizadas más fácilmente y de manera más eficiente, lo que reduce el tiempo de espera para las actualizaciones de los usuarios finales
Integración Limitada del Escritorio Problemas con la integración de aplicaciones Flatpak en escritorios Linux, como la falta de soporte para configuraciones de fuentes y temas de escritorio. Se han mejorado los mecanismos de integración de escritorio, incluyendo mejor soporte para fuentes y temas en KDE y GNOME. Flatpak sigue trabajando en mejorar la compatibilidad con configuraciones del sistema host.)
Explotación Local de Root Riesgo de explotación de root local debido a aplicaciones Flatpak instaladas con permisos suid. Flatpak utiliza nuevas APIs de libostree para rechazar cualquier archivo con permisos suid o de escritura mundial al instalar aplicaciones.
Gestión de Permisos Confusa para el Usuario Los usuarios pueden ser engañados por íconos y descripciones que indican un falso sentido de seguridad sobre las aplicaciones sandboxed. Flatpak ha mejorado la interfaz de usuario para mostrar de manera más clara los permisos que solicita cada aplicación, ayudando a los usuarios a tomar decisiones informadas sobre las aplicaciones que instalan.


Sitio de Flathub mostrando las advertencias pertinentes a una determinada aplicación

¿Cómo hace flatpak para no pedirle a un usuario de un grupo administrativo que se autentique nuevamente?

En el post anterior vimos como funciona polkit. Flatpak se vale de polkit para facilitar la instalación de paquetes por parte de usuarios con permisos administrativos. Basta con mirar el archivo /usr/share/polkit-1/rules.d/org.freedesktop.Flatpak.rules.

polkit.addRule(function(action, subject) {
    if ((action.id == "org.freedesktop.Flatpak.app-install" ||
         action.id == "org.freedesktop.Flatpak.runtime-install"||
         action.id == "org.freedesktop.Flatpak.app-uninstall" ||
         action.id == "org.freedesktop.Flatpak.runtime-uninstall" ||
         action.id == "org.freedesktop.Flatpak.modify-repo") &&
        subject.active == true && subject.local == true &&
        subject.isInGroup("wheel")) {
            return polkit.Result.YES;
    }

    return polkit.Result.NOT_HANDLED;
});

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.Flatpak.override-parental-controls") {
            return polkit.Result.AUTH_ADMIN;
    }

    return polkit.Result.NOT_HANDLED;
});

Este archivo tiene dos reglas:

  • Permite a los usuarios del grupo "wheel", que están loogoueados localmente y tienen una sesión activa instalar, desinstalar, y modificar aplicaciones y repositorios sin autenticación adicional.
  • No se pueden pasar por arriba de los controles parentales sin autenticarse con una cuenta de usuario administrativa.

Para ambos casos, si las condiciones no se cumplen, el resultado dependerá de otras reglas o de la regla predeterminada.

Para terminar...

Flatpak elimina barreras y obstáculos tanto para desarrolladores como para usuarios finales. Para los primeros, significa les ofrece la posibilidad de poner aplicaciones disposición de cualquier distribución de Linux. Para los segundos representa una mayor facilidad para obtener los programas que están necesitando.

Discover mostrando aplicaciones disonibles en Flathub

Fuentes y Más Recursos

  1. Flatpak Documentation - Using Flatpak
  2. Flathub
  3. Flatkill - Flatpak, a security nightmare
  4. Respuestas a objecioones planteadas en Flatkill
  5. Flatpak en Fedora

Entendiendo Polkit

Introducción

El esquema de permisos en Linux es simple:

  • Un superusuario con todos los poderes para leer, modificar y ejecutar todo.

  • El resto de los usuarios con permisos limitados.

  • Es decir, la clasificación de usuarios en tres categorías, el famoso UGO (User, Group, Others) y el trío de permisos: rwx (read, write y execution).

Pero esa simpleza tiene sus limitaciones, como vimos ya anteriormente, con el paso del tiempo se han ideado maneras de hacer esos permisos más granulares y que superusuario root pueda delegar atribuciones.

Una herramienta muy importante en este sentido es polkit (antiguamente conocido como PolicyKit). En este post vamos a explorar para develar qué es polkit, como funciona, y como se diferencia de sudo. Además, en el próximo post, veremos la relación que tiene con flatpak.

¿Qué es polkit?

Polkit

Leer más…

¿Qué significa en Linux que todo es archivo?

En un viejo artículo, hay una presentación en la que mencioné una frase bastante habitual en el mundo Unix/Linux: «Todo es archivo». Vamos a explicar qué significa realmente esa frase.

Todo es archivo

¿Todo es Archivo?

En realidad, ese dicho no es del todo correcto. Sin embargo, proviene de lo que hoy tal vez podríamos llamar la filosofía Unix. Probablemente, nadie dijo esa frase de manera literal en los primeros años de esa familia de sistemas operativos. Aun así, ideas de ese estilo estaban en la mente desde los comienzos y en los trabajos de por ejemplo, Bill Joy (pionero de TCP/IP). Linus Torvalds, no estuvo ausente en esta discusión. Y mucho más cerca en el tiempo, Lennart Poettering, retoma esa idea una vez más para defender el trabajo realizado en systemd. Por si no se dieron cuenta dice Poettering la interfaz de cgroups están expuesta en un pseudo-sistema de archivos...

Sin embargo, es más apropiado decir que todo es un descriptor de archivo o que todo puede tener un descriptor de archivo. En Linux un descriptor de archivo es un identificador único de proceso para un archivo u otro recurso de entrada o salida. Por ejemplo, no existe necesariamente un archivo para una interfaz de red. Sin embargo, existen sockets relacionados con ellas.

En el siguiente ejemplo podemos ver los descriptores de archivos para la dirección ip 127.0.0.1 de la interfaz lo.

sudo lsof -n -i@127.0.0.1
COMMAND    PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
chronyd   1337 chrony    5u  IPv4   8171      0t0  UDP 127.0.0.1:323 
cupsd     1538   root    7u  IPv4  14727      0t0  TCP 127.0.0.1:ipp (LISTEN)
qemu-syst 1774   qemu   32u  IPv4  19480      0t0  TCP 127.0.0.1:rfb (LISTEN)
syncthing 2620 sergio   19u  IPv4  22825      0t0  TCP 127.0.0.1:8384 (LISTEN)

En este caso, vemos los descriptores de archivos de lectura y escritura 5u, 7u, 32u y 19u.

Para ver la relación entre descriptores y sockets podemos hacer un ping a una determinada dirección ip:

ping -c 10000 9.9.9.9 &> /dev/null

Si buscamos los descriptores de archivos con lsfd, mostrará el siguiente resultado:

lsfd -p 914323 -Q '(FD >= 0)' 
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping    914323 sergio     0 rw-D--    CHR  pts:6    36        9 /dev/pts/6 
ping    914323 sergio     1 -w----    CHR  mem:3    34        4 /dev/null
ping    914323 sergio     2 -w----    CHR  mem:3    34        4 /dev/null
ping    914323 sergio     3 rw---m   PING sockfs     9 30437500 state=close id=35 laddr=0.0.0.0
ping    914323 sergio     4 rw---- PINGv6 sockfs     9 30437501 socket:[30437501]

Analicemos cada descriptor de archivo:

  • 0: Es la "standard input" que apunta a una pseudoterminal (dispositivo de caracteres), al tratarlo como un archivo tiene permisos: de lectura y escritura. Usa un sistema de archivos y como tal también maneja inodos.
  • 1: Es la "standard output" que apunta a /dev/null. Solamente tiene permisos de escritura, el sistema de archivos proviene de la memoria principal y tiene permisos solamente de escritura.
  • 2: Es el "standard error" que apunta también a /dev/null.
  • 3: Apunta a un socket de tipo IPv4, y tiene permisos de lectura y además está siendo utilizado por un evento de multiplexación.
  • 4: Finalmente tenemos el descriptor de 5 que apunta a un tipo de socket que usa IPv6.

Dentro del directorio /proc podemos ver que los descriptores 1 (salida estándar) y 2 (error estándar) se redirigen al archivo /dev/null, mientras tanto, 3 y 4 apuntan a sockets:

ls -l /proc/914323/fd
total 0
lrwx------ 1 sergio sergio 64 may 19 17:33 0 -> '/dev/pts/6 (deleted)'
l-wx------ 1 sergio sergio 64 may 19 17:33 1 -> /dev/null
l-wx------ 1 sergio sergio 64 may 19 17:33 2 -> /dev/null
lrwx------ 1 sergio sergio 64 may 19 17:33 3 -> 'socket:[30437500]'
lrwx------ 1 sergio sergio 64 may 19 17:33 4 -> 'socket:[30437501]'

Podemos hacer otra prueba, en este caso usaremos solamente ping sobre ipv6 y en lugar de redireccionar a /dev/null, lo haremos redirigiendo stdin y stderr a archivos regulares distintos:

ping6 -c 10000 ::1 > out.txt 2> errors.txt &

Aquí solamente usará un socket (ya que no usa ipv4):

lsfd -p 926928 -Q '(FD >= 0)'
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping6   926928 sergio     0 rw----    CHR  pts:7    36       10 /dev/pts/7
ping6   926928 sergio     1 -w----    REG   0:34    52 38207299 /tmp/out.txt
ping6   926928 sergio     2 -w----    REG   0:34    52 38207300 /tmp/errors.txt
ping6   926928 sergio     3 rw---- PINGv6 sockfs     9 31193485 state=close id=44 laddr=::

¿Qué sucede si se borra /tmp/errors.txt?

lsfd -p 926928 -Q '(FD >= 0)'
COMMAND    PID   USER ASSOC  XMODE   TYPE SOURCE MNTID    INODE NAME
ping6   926928 sergio     0 rw----    CHR  pts:7    36       10 /dev/pts/7
ping6   926928 sergio     1 -w----    REG   0:34    52 38207299 /tmp/out.txt
ping6   926928 sergio     2 -w-D--    REG   0:34    52 38207300 /tmp/errors.txt
ping6   926928 sergio     3 rw---- PINGv6 sockfs     9 31193485 state=close id=44 laddr=::

Bueno, veremos que el archivo aparece con el flag D indicando que borró del respectivo sistema de archivos.

Por lo tanto, si bien literalmente no todo es archivo, podemos afirmar que en general podríamos mediante los descriptores de archivos establecer un puente entre un proceso y los recursos a los que está accediendo. En definitiva, un descriptor de archivos, permite al proceso comunicarse con el recurso subyacente de manera eficiente y uniforme, independientemente de si el recurso es un archivo en el sistema de archivos, un dispositivo de hardware o un socket de red.

Enlaces útiles

Gestión de Contraseñas Usando Contenedores Podman

Podemos administrar nuestros datos personales también con estándares profesionales. En este contexto, vamos a explorar cómo llevar la gestión de contraseñas usando utilizando herramientas viejas y confiables como pass, gpg, git combinada con la potente solución de contenedores Podman. Será una experiencia desafiante.

Claves

Almacenamiento de Contraseñas Elevado

Para gestionar nuestras passwords usaremos pass. La herramienta pass no estra cosa que un potente script de bash de algo más de 720 líneas de wrapper principalemente para gpg y git.

Ahora, lo realmente interesante: configuraremos un repositorio dentro de un contenedor Podman. Este repositorio, administrado con git, puede ser compartido de manera segura y efectiva en todos tus dispositivos.

Acceso Simplificado desde Cualquier Lugar

La accesibilidad es clave en tu entorno profesional. Ya sea trabajando en sistemas Windows o Linux, puedes aprovechar QtPass, una interfaz gráfica para pass. Para usuarios de iPhone, passforios es una excelente opción, mientras que los dispositivos Android pueden beneficiarse de Password-Store, disponible en Google Play Store y F-Droid para mantener tus contraseñas siempre sincronizadas.

Esquema de podman con pass

Características Clave para Profesionales

  • Control Total: Somos nosotros quienes gestionan nuestros repositorios. Los datos sensibles están en tus dispositivos, evitando intermediarios y asegurando la confidencialidad.

  • Portabilidad Sin Esfuerzo: Se puede cambiar de dispositivos o realiza copias de seguridad con facilidad. Tu repositorio te seguirá sin problemas.

  • Seguridad de Alto Nivel: tus credenciales seguras con estándares de confiabilidad, integridad y disponibilidad.

  • Compartir con Confianza: Podrás compartirlo fácilmente en círculos de confianza.

Configuración Inicial y Uso Avanzado

En esta primera fase, te guiaremos a través de la configuración de tu repositorio en un contenedor Podman y las opciones para interactuar con él desde distintos dispositivos.

Nota 1:

El enfoque primordial es establecer un almacén personal de contraseñas. Además de utilizarlo en tus dispositivos, podrás extender su uso a usuarios de confianza. Aunque podría requerirse alguna asistencia técnica inicial para la configuración, las acciones esenciales son "actualizar desde el repositorio (pull)" y "enviar cambios al repositorio (push)".

Esta idea es adaptable a ambientes profesionales con para grupos reducidos de usuarios en una red corporativa, donde cada miembro tiene su copia del repositorio principal. El repositorio podría estar incluso en algún contenedor que esté disponible 24x7. Esto facilita la consulta, creación, modificación y eliminación colaborativa de contraseñas, consolidando luego los cambios en un repositorio compartido.

Nota 2:

Para aprovechar plenamente esta guía, asumimos que poseés conocimientos de:

  • Gestión de claves públicas ssh
  • Gestión de claves gpg
  • Uso básico de git
  • Uso básico de Podman
  • Conocimientos generales de Linux: bash, systemd, firewalld (iptables/nftables), etc.

¿Qué usaremos para lograr todo esto?

  • OS: Fedora 38
  • podman
  • pass

Se podría usar otra distribución, sin embargo es importante que cuente con una versión relativamente reciente de podman.

¿Por qué usamos podman? Porque tiene una gran similitud con docker, y además, posee la capacidad de correr contenedores de manera mucho más segura y sencilla. En este caso particular, crearemos un contenedor que alojará el repositorio compartido de passwords.

No será necesario crear servicios web y/o de bases de datos.

Instalación de paquetes

dnf install -y git pass

Inicialización de repositorio de passwords

En este ejemplo le pasamos los identificadores de las clave públicas gpg en el siguiente usamos 3 direcciones de mail correspondientes a la clave del host, la del celular y la de otra persona respectivamente.

pass init jkm@example.com fxi@example.com pyn@example.net

pass git init

pass generate puertablanca

Preparación del container

Creamos el siguiente Dockerfile

FROM fedora:38
RUN dnf update -y && dnf install -y git openssh-server
RUN useradd -ms /bin/bash git
RUN mkdir /home/git/.ssh
RUN ssh-keygen -A
COPY  ssh-pks /home/git/.ssh/authorized_keys
RUN echo "git ALL=(ALL:ALL) NOPASSWD: /usr/sbin/sshd" >> /etc/sudoers && git clone --bare /mnt/.password-store /home/git/.password-store && chown -R git:git /home/git  && chmod 700 /home/git/.ssh && chmod 600 /home/git/.ssh/authorized_keys
EXPOSE 22
CMD ["sudo","/usr/sbin/sshd", "-D"]

Esto nos permitirá crear un repositorio con las siguientes características:

  • Basado en Fedora 38
  • Un usuario llamado git que tendrá el único privilegio de correr el servicio ssh
  • Obtendrá una copia del repositorio git antes creado con pass

Crear la imagen de podman

podman build -v /home/sergio/.password-store:/mnt/.password-store -t passteiner .

Crear el container

podman run -d --name container-pass_git --user git -p 60003:22 passteiner

En este punto, ya estamos en condiciones de crear, editar, modificar nuestras passwords y subirlas al repo del contenedor de manera que esté disponible para otros dispostivos y/o usuarios.

Cabe aclarar que para usar Password Store en Android hace falta instalar OpenKeychain. Esa aplicación nos permitirá crear un el par de clave privada + clave pública GnuPG, como así también importar la clave pública del resto de los usuarios con quien compartiremos el llavero. Tener en cuenta que es muy importante la passphrase que usemos para cifrar nuestra clave privada: debe ser fácil de memorizar y a la vez robusta. Esa misma passphrase se nos pedirá cuando necesitamos acceder a las contraseñas:

Y luego con Password Store tendremos que:

  • Ingresar los parámetros de repositorio que está en el contenedor que hemos creado, tanto la url (por ejemplo ssh://git@10.0.0.10:60003/git/,password-store) y la branch (master).
  • Generar la clave pública ssh para autenticarse al repositorio. Esta clave tendrá que copiarse al archivo /home/git/.ssh/authorized_keys del contenedor.

podman exec ontainer-pass_git bash -c 'echo "clave_publica_ssh" /home/git/.ssh/authorized_keys'

(Si queremos que este archivo sea persistente, podríamos modiicar el Dockerfile para que use un volumen).

  • Clonar el repositorio del contenedor.

Una vez que hemos clonado el repositorio, obtendremos el listado de passwords y el menú para operar con él:

Menú de Password Store.

Actualizando nuestro repositorio

Hay varias configuraciones posibles, pero la que recomiendo es la siguiente:

pass git config pull.rebase false

Para bajar las actualizaciones del contenedor:

pass git pull

Para subir las propias modificaciones al contenedor:

pass git push

Comentario finales

Al finalizar habrás conseguido que:

  • El contenedor se ejecuta como un usuario sin privilegios dentro del sistema.

  • Todo - excepto el proceso sshd - se ejecutará como un usuario sin privilegios aun dentro del container. Salvo que explícitamente uses algo como docker run --user root..... . Pero ¿por qué lo harías?

  • Por fuera del contenedor en realidad, mapea a nuestro propio usuario.

Enlaces útiles

3 pasos para configurar unidad de systemd para VM de Virtualbox

Bonita imagen ilustrativa 😄

Foto de Ashutosh Dave en Unsplash

1. Encontrar el UUID de la VM, por ejemplo si tenemos una máquina llamada RHEL7, ejecutamos:

$ VBoxManage showvminfo RHEL7 | grep '^UUID' | awk '{ print $NF }'
f02a9f08-2ff2-4a92-b3cd-a8dfb17513c6

2. Crear el archivo template de servicio ~/.config/systemd/user/virtualbox_vm@.service:

[Unit]
Description=VirtualBox VM %i

[Service]
Type=simple
KillMode=mixed
ExecStart=/usr/lib/virtualbox/VBoxHeadless --comment RHEL7 --startvm f02a9f08-2ff2-4a92-b3cd-a8dfb17513c6 --vrde config

[Install]
WantedBy=default.target

3. Recargar systemd, Habilitar y arrancar el servicio

$ systemctl --user daemon reload && systemctl --user enable --now virtualbox_vm@RHEL7.service

¡Listo!

Tu propia nube

¿Qué es Syncthing?

Imaginemos el siguiente escenario: una aplicación de notas que usamos en nuestra computadora personal. ¿Cómo hacemos para mantener esas notas sincronizadas con el celuar? O por ejemplo queremos compartir esas notas con alguien de la familia. ¿No es demasiado recurrir a la Nube para eso? Es decir tenemos notas, fotos, etc. privadas que no queremos recurrir a un tercero para que se sitúe como mediador para que esos documentos estén sincronizados y compartidos. A fin de cuentas, la intimidad es un valor... ¿La intimidad es un valor? Bueno, muchos en pleno siglo XXI creemos que lo sigue siendo. De modo que si considerás la intimidad como un valor a cuidar, la aplicación Syncthing te resultará de mucha utilidad.

Foto de Dayne Topkin en Unsplash

¿Qué tecnología usa Syncthing?

Synthing usa BEP, un protocolo que se usa entre dos o más dispositivos para formar un cluster. Cada dispositivo intenta tener sus carpetas sincronizadas con la versión más reciente del cluster. Toda la comunicación se asegura mediante TLS con Perfect Forward Secrecy para impedir que los datos sean descifrados, sea en sesiones pasadas o futuras, aun si las claves privadas usadas en una sesión individual se roban en algún momento.

¿Qué necesitamos instalar?

En primer lugar el paquete syncthing, por ejemplo:

dnf install syncthing

Este paquete tiene el binario para lanzar el servidor.

Como usuario se puede habilitar y lanzar con:

systemctl --user enable --now syncthing.service

Se puede instalar en en teléfonos móviles con Android tanto desde el Play Store como desde el repositorio F-Droid.

¿Cómo realizamos la configuración?

La configuración se puede realizar mediante el acceso a la interfaz web que escucha de manera predeterminada en 127.0.0.1:8384.

Es muy importante elegir un dispositivo que funcione como presentador o introducer. El presentador es el encargado de agregar automáticamente otros dispositivos. Solamente debe haber un presentador por cluster.

Si usamos firewalld, se pueden habilitar los puertos que usa synthing:

firewall-cmd --add-service=syncthing --permanent && firewall --reload

Paquetes adicionales

Además, hay otros paquetes muy útiles que se puede instalar usando el repositorio home_mkittler:

syncthingfileitemaction

Opciones de syncthing en Dolphin Añade opciones al menú contextual en Dolphin.

syncthingplasmoid

Plasmoid de syncthing Es un módulo de plasma que permite visualizar, controlar y configurar Syncthing.

syncthingtray

Acceso a syncthing desde la bandeja del sistema Es similar a syncthingplasmoid pero solamente se ancla en la bandeja del sistema.

Conclusión

Si bien Synthing no debe entenderse como una solución de fileserver corporativa pero alcanza con creces para montar de manera rápida una nube personal, preservando principios fundamentales de seguridad.

Referencias y más información

Plan Táctico y Estratégico de la Memoria en Linux

La mitología en torno a la memoria en Linux ha producido una serie de relatos:

  • Linux puede funcionar con muy poca memoria RAM.
  • Linux consume mucha memoria.
  • Una partición SWAP debe tener entre 1 a 2 veces la memoria RAM.

Como vemos algunas historias son más recientes, otros más antiguas, pueden ser parcialmente ciertas y hasta contradictorias entre sí.

Este artículo tiene como propósitos:

  • Explicar de manera sencilla el funcionamiento de la memoria en Linux, desmitificando también algunos conceptos.
  • Enumerar y describir tácticas para que el uso de la memoria proporcione la mejor usabilidad y experiencia del usuario.
  • Ofrecer alternativas para que cada uno elija la mejor opción de acuerdo a sus necesidades.

Definiciones

Vamos a repasar algunos conceptos básicos que de manera más o menos frecuente usamos, usaremos metáforas en el camino. Ninguna metáfora es perfecta, pero nos ayudan a entender la realidad.

Memoria Virtual

Generalmente el adjetivo virtual en informática significa algo que provoca la ilusión de ser otra cosa. Por ejemplo, un archivo regular puede hacerse pasar por un disco físico. En el caso de la memoria virtual, el sistema operativo nos ofrece una cantidad de memoria mucho mayor a la que existe físicamente.

Si queremos comprar algo que sale $100000 pero solamente podemos pagar la décima parte, tal vez podamos pedir un crédito para que se financie la compra y pagar $10000 por mes. El sistema operativo hace algo parecido con la memoria.

De parte del hardware en particular la CPU necesita poder traducir las direcciones de memoria virtual a la memoria física.

Algo muy importante: Linux trata de usar la mayor cantidad de memoria posible, para poder ejecutar las aplicaciones y acceder a los archivos de la manera más rápida posible. De manera que si la memoria libre es baja no es necesariamente un indicativo de un problema.

Swap

Los usuarios ocasionales de Linux y aun muchos sysadmins tienen una idea negativa sobre "la swap". Simplificaciones extremas y conceptos anticuados la han convertido en le gran villana de la historia del sistema operativo.

Si comparamos a la memoria con un escritorio, sin swap podría lucir así:

Prescindir de swap no es una opción sana.

Photo by Ashim D’Silva on Unsplash

Así que primero vamos a decir lo que no es:

  • No es la memoria virtual sino que forma parte de la técnica que realiza el sistema operativo para administrar la memoria.
  • No es un espacio de reserva ni un último recurso.
  • No es algo que el sistema operativo pueda alegremente prescindir aun cuando la cantidad de memoria RAM física sea grande.

Nuestro escritorio con swap:

Analogía de Swap

Photo by Alexandru Acea (edited by me) on Unsplash

¿Los cajones de un escritorio los usamos cuando lo tenemos abarrotado de cosas? No, los usamos para guardar cosas que no son de alta prioridad. Aunque es cierto, si luego queremos usar esa tijera o aquel destornillador en algún momento requerirá un poco más de trabajo, tendremos que abrir el cajón, buscarlo, extraerlo, etc.

Ah, y la swap también sirve para hibernar, aunque honestamente no se cuanta gente mantiene esa práctica.

Page

Es un bloque de memoria virtual.

Page table

Podemos pensarlo como un índice usado por el hardware que refiere cada dirección de memoria virtual a una dirección de memoria física.

Page Fault

Una page fault ocurre cuando un programa intenta acceder a un bloque que está mapeado en el espacio de direcciones pero no está cargado en la RAM. Si bien no es un problema grave, implica que el programa tendrá que recuperar la información desde el disco, que es un proceso más lento.

Page cache

Es el espacio en que los archivos y metadatos utilizados suelen guardarse para poder acceder a ellos de manera más rápida.

Page cache

Tipos de memoria

File Memory

Es la memoria relacionada con el Page Cache.

Anonymous Memory

El adagio unixista (en su versión simplificada) que dice que todo es archivo parece tener influencia aquí. Tal vez es por eso que la memoria que no está asociada a un archivo o al sistema de archivos se la reduce con el adjetivo de anónima.

Thrashing

Es cuando el sistema agresivamente traduce direcciones físicas a direcciones virtuales y libera bloques de memoria que no se han usado de manera reciente. La ejecución normal de las tareas puede resentirse. En entornos de escritorio la usabilidad y la experiencia del usuario se ven fuertemente afectadas. La swap puede colaborar evitando al menos un poco el thrashing. A veces puede suceder cuando la memoria física (RAM) es insuficiente.

Memory Pressure

Memory pressure es el trabajo que tiene que hacer Linux cuando hay déficit de memoria. En ciertas situaciones puede haber demora en la ejecución de tareas, y que el rendimiento se vea seriamente afectado y llevado al extremo puede causar OOM (Out-of-Memory): el agotamiento total de la memoria para que el sistema operativo pueda continuar funcionando correctamente.

OOMKiller

El OOM killer es un proceso del kernel que se dispara solamente si la memoria disponible bajó a niveles críticos, en este escenario selecciona una o más tareas para finalizarla con la intención de que el sistema pueda seguir funcionando.

oom_score

A cada proceso se le asigna un puntaje, cuanto más alto es, más susceptible es a ser terminado por OOMKiller. El comando choom permite ver y/o ajustar dicho valor.

choom


Tuning

Ahora veremos diferentes tácticas que podemos usar para optimizar el uso de la memoria.

cgroupv2

cgroup es un mecanismo para organizar los procesos de manera jerárquica y distribuir los recursos del sistema a lo largo de la jerarquía en una manera controlada y configurada.

Un cgroup se compone de un núcleo que es responsable primariamente en organizar de manera jerárquica los procesos y controladores que comúnmente distribuyen un tipo específico de recurso del sistema a lo largo de la jerarquía.

En la versión 2 de cgroup un proceso no puede pertenecer a diferentes grupos para diferentes controladores. Si el proceso se uno al grupo alfa, todos los controladores para alfa tomarán control de ese proceso.

ps mostrando cgroup

Supongamos que los procesos de un cgroup (y todos los grupos hijos) usan poca memoria, podríamos decirle al kernel que reclame memoria de otros cgroups. Esto es precisamente lo que hace el parámetro memory.low.

el parámetro memory.low

Otro parámetro interesante para monitorear es memory.pressure, la primera línea tiene el tiempo físico de una o más tareas demoradas debido a la falta de memoria. La segunda sería lo mismo pero para todas las tareas del grupo, full es lo mismo pero para todas las tareas del grupo, Entonces si miramos el archivo /sys/fs/cgroup/user.slice/memory.pressure:

some avg10=0.00 avg60=0.13 avg300=0.12 total=1690238
full avg10=0.00 avg60=0.10 avg300=0.09 total=1394199

Significa que algunas tareas del grupo de control user.slice en los últimos 10 segundos no tuvo demoras, pero si tuvo un 0,13% de retraso en el último minuto y 0,12% en los últimos 5 minutos. En total estas tareas llevan acumulados casi 1,7 segundos. La segunda línea representa lo mismo pero para todas las tareas del grupo.

zram

zram es por así decirlo, una manera cool de usar swap gracias a un módulo del kernel. zram, swap pero cool

Photo by chuttersnap on Unsplash

En lugar de gastar espacio en un disco (sea rígido o sólido) usamos dispositivos de bloque en la propia RAM. Los bloques swapeados se guardan comprimidos. Esto discos virtuales son rápidos y ahorran memoria.

Una de las pocas desventajas que tiene esta metodología es la incapacidad para poder hibernar el sistema operativo, al no estar presente la partición en un almacenamiento de tipo persistente.

zram

Sistemas de archivos

El journal de ext4 puede ser lento, xfs puede ser una mejor alternativa o mejor aun btrfs.

EarlyOOM

El oom-killer del kernel solamente se dispara en situaciones extremas y le puede llevar mucho tiempo hasta que puede enviar SIGKILL a los procesos que sean necesarios para poder liberar memoria. Durante ese tiempo probablemente el usuario no pueda interactuar con el sistema operativo.

EarlyOOM trabaja en espacio de usuario y por lo tanto se puede anticipar y ser mucho más rápido.

El comportamiento predeterminado en Fedora es que si hay menos del 10% de RAM y/o SWAP libre, earlyoom envía una señal de terminación a todos los procesos con oom_score más alto. Si la RAM como SWAP libre bajan por debajo del 5%, earlyroom enviará una señal para matar todos los procesos con oom_score más elevado.

La idea es recuperar la usabilidad (especialmente en un entorno de escritorio) lo antes posible.

El problema es que EarlyOOM no soporta al momento la medición de la memory pressure como indicativo para tomar decisiones.

nohang

Este servicio es mucho más configurable y aporta una mejor solución que EarlyOOM.

Algunas funcionalidad son:

  • Se puede elegir la acción que realizará en una situación OOM.
  • Ofrece varios criterios para elegir los procesos a finalizar.
  • Soporta zram
  • Puede usar memory pressure para tomar una acción.
  • El archivo de configuración es medianamente sencillo

zswap

Con zswap no reemplazamos el espacio swap en el disco sino que usamos un caché comprimido en la RAM. Este método ahorra I/O, obteniendo entonces mejor rendimiento y alargando la vida útil de discos flash o sólidos. La única desventaja es usar algo de tiempo del procesador para realizar la compresión.

zswap

Photo by Pineapple Supply Co. on Unsplash

Mediante el caché se logra una diferenciación entre páginas más usadas (zswap) y menos usadas (swap).

oomd

El servicio oomd es un proyecto en el que están trabajando en Facebook para integrarlo con systemd. Por ahora es un proyecto para manejo de memoria a gran escala, y bastante más complejo de configurar.

Resumen

  • Swap no es la villana de la película
  • Si existe la opción de migrar a otros sistema de archivos aunque con características un tanto experimental, elegir btrfs. Una opción más moderada es xfs.
  • El tuning de cgroupv2 puede traer grandes beneficios, no obstante existen proyectos y distribuciones que no lo usan.
  • EarlyOOM es una solución rápida y aplicable a una amplia gama de sistemas Linux, aunque no siempre es la más exacta ni más elegante.
  • El servicio nohang (o no hang-desktop) es una opción más madura aunque algo más compleja que EarlyOOM.
  • El servicio oomd desarrollado por Facebook es seguramente la opción más adecuada para escenarios más complejos y de manejo de memoria a gran escala.
  • Si se desea ahorrar espacio en disco se puede reemplazar la swap por zram, sacrificando la opción de hibernar el sistema.
  • La opción zswap es más sofisticada, aunque dependemos del uso de swap en disco.

Photo by sk on Unsplash

Fuentes consultadas

Tutorial: Cifrar $HOME con gocryptfs

En un artículo anterior: Tutorial de fscrypt para cifrar archivos, habíamos visto como cifrar archivos con fscrypt y ext4. Ahora aprenderemos otro método independiente del sistema de archivos utilizado, se trata de una herramienta llamada gocryptfs.

Conocimientos previos necesarios:

  • Uso habitual de línea de comandos en Linux (incluyendo entre otros manejo de propietarios y permisos)
  • Instalación y desinstalación de paquetes

En el ejemplo en cuestión estoy usando Debian Buster (te recomiendo primero instalarla en una máquina virtual para hacer pruebas), de modo que los pasos a seguir pueden ser un poco diferentes en otras distribuciones, pero los principios generales se mantienen. Todos los pasos hasta que lo pruebes como usuario común deben hacerse con privilegios de superusuario.

Es muy importante contar espacio suficiente para copiar temporalmente los archivos del directorio que se desea cifrar.

He cambiado el shell del usuario sergio que es dash (predeterminado en Debian) por bash, ya que el primero no está pensado para un uso interactivo habitual además de ser menos potente.

Instalar grocryptfs

apt install grocryptfs

Crear el directorio para cifrar

mkdir /home/sergio_cifrado

Inicializar el directorio

Aquí seteamos la misma contraseña que la del usuario

gocryptfs --init /home/sergio_cifrado/
Choose a password for protecting your files.
Password: 
Repeat: 

Your master key is:

    9c43faf4-16a07508-42213628-50a5c55e-
    e0c17483-c41453a0-6355f9f0-897b3aa9

If the gocryptfs.conf file becomes corrupted or you ever forget your password,
there is only one hope for recovery: The master key. Print it to a piece of
paper and store it in a drawer. This message is only printed once.

The gocryptfs filesystem has been created successfully.
You can now mount it using: gocryptfs /home/sergio_cifrado MOUNTPOINT

Montar el directorio

mkdir /home/sergio_montaje_temporario && chmod 750 /home/sergio_montaje_temporario && chown sergio. /home/sergio_temporario &&
gocryptfs /home/sergio_cifrado/ /home/sergio_temporario/
Password: 
Decrypting master key
Filesystem mounted and ready.

Ajustamos los propietarios y permisos

chown -R sergio. /home/sergio{_cifrado} && chmod 750 /home/sergio_cifrado

Copiar todos los archivos del directorio del usuario al directorio temporal

cp -Tav /home/sergio /home/sergio_temporario

Borrar el contenido del directorio del usuario (por favor realizar previamente un backup)

rm -rf /home/sergio

Desmontar el directorio cifrado

fusermount -u /home/sergio_temporario

Le cambiamos el nombre al directorio temporario por el original

mv /home/sergio_temporario /home/sergio

Todo lo que viene a continuación es necesario cuando queremos que el directorio se monte de manera automática en el momento del login.

Instalamos el módulo de PAM para montaje de volúmenes

apt install -y libpam-mount

Ejecutamos el configurador de pam

pam-auth-update

pam-auth-update

y presionamos en Aceptar.

En otras distribuciones y configuraciones, puede ser necesario editar otros archivos del directorio /etc/pam.d.

Luego hay que editar el archivo /etc/security/pam_mount.conf.xml agregando lo siguiente antes de </pam_mount>:

<volume user="sergio" fstype="fuse" options="nodev,nosuid,quiet,nonempty,allow_other"
path="/usr/bin/gocryptfs#/home/%(USER)_cifrado" mountpoint="/home/%(USER)" />

Configuramos FUSE

# /etc/fuse.conf - Configuration file for Filesystem in Userspace (FUSE)

# Set the maximum number of FUSE mounts allowed to non-root users.
# The default is 1000.
#mount_max = 1000

# Allow non-root users to specify the allow_other or allow_root mount options.
# Modificamos aquí:
user_allow_other

Eso es todo, felicitaciones si llegaste hasta aquí 😀, podemos reiniciar y probar la configuración:

Para login gráfico:

Conclusión

De esta manera pudiste configurar el cifrado automática de un directorio $HOME de un usuario. Este método es principalmente útil para equipos donde trabaja un solo usuario (podría ser tu laptop de trabajo diario por ejemplo).

Fuentes y más recursos