GNU/Linux >> Tutoriales Linux >  >> Linux

Construyendo un contenedor de Linux a mano usando espacios de nombres

No hace mucho, escribí un artículo que cubría una descripción general de los espacios de nombres más comunes. Es genial tener la información y, hasta cierto punto, estoy seguro de que puede extrapolar cómo puede hacer un buen uso de este conocimiento. Normalmente no es mi estilo dejar las cosas tan abiertas. Entonces, para los próximos artículos, dedico un tiempo a demostrar algunos de los espacios de nombres más importantes a través de la lente de la creación de un contenedor primitivo de Linux. En cierto sentido, estoy escribiendo mis experiencias con las técnicas que utilizo al solucionar problemas de un contenedor de Linux en el sitio de un cliente. Con eso en mente, empiezo con la base de cualquier contenedor, especialmente cuando la seguridad es una preocupación.

Un poco sobre las capacidades de Linux

La seguridad en un sistema Linux puede tomar muchas formas. Para los propósitos de este artículo, me preocupa principalmente la seguridad cuando se trata de permisos de archivo. Como recordatorio, todo en un sistema Linux es una especie de archivo y, por lo tanto, los permisos de archivo son la primera línea de defensa contra una aplicación que puede comportarse mal.

La forma principal en que Linux maneja los permisos de archivo es a través de la implementación de usuarios . Hay usuarios normales, para los que Linux aplica verificación de privilegios, y existe el superusuario que pasa por alto la mayoría (si no todos) los controles. En resumen, el modelo original de Linux era todo o nada.

Para evitar esto, algunos programas binarios tienen el set uid poco establecido en ellos. Esta configuración permite que el programa se ejecute como el usuario propietario del binario. La passwd La utilidad es un buen ejemplo de esto. Cualquier usuario puede ejecutar esta utilidad en el sistema. Necesita tener privilegios elevados en el sistema para interactuar con la shadow archivo, que almacena los valores hash de las contraseñas de los usuarios en un sistema Linux. Mientras que la passwd binary tiene verificaciones integradas para garantizar que un usuario normal no pueda cambiar la contraseña de otro usuario, muchas aplicaciones no tienen el mismo nivel de escrutinio, especialmente si el administrador del sistema activó el set uid poco.

Linux capacidades se crearon para proporcionar una aplicación más granular del modelo de seguridad. En lugar de ejecutar el binario como root, puede aplicar solo las capacidades específicas que una aplicación requiere para ser efectiva. A partir de Linux Kernel 5.1, hay 38 capacidades. Las páginas man para las capacidades están bastante bien escritas y describen cada capacidad.

Un conjunto de capacidades es la forma en que se pueden asignar capacidades a los subprocesos. En resumen, hay cinco conjuntos de capacidades en total, pero para esta discusión, solo dos de ellos son relevantes:Efectivo y Permitido.

Efectivo :El núcleo verifica cada acción privilegiada y decide si permite o no una llamada al sistema. Si un hilo o archivo tiene el efectivo capacidad, se le permite realizar la acción relacionada con la capacidad efectiva.

Permitido :las capacidades permitidas aún no están activas. Sin embargo, si un proceso ha permitido capacidades, significa que el proceso mismo puede optar por escalar su privilegio a un privilegio efectivo.

Para ver qué capacidades puede tener un proceso determinado, puede ejecutar getpcaps ${PID} dominio. La salida de este comando se verá diferente según la distribución de Linux. En RHEL/CentOS obtendrá una lista completa de capacidades:

[root@CentOS8 ~]# getpcaps $$
Capabilities for `1304': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39+ep

Si ejecuta el comando man 7 capabilities , encontrará una lista de todas estas capacidades junto con una descripción de cada una. En algunas distribuciones, como Ubuntu o Arch, ejecutar el mismo comando simplemente da como resultado esto:

[root@Arch ~]# getpcaps $$
414429: =ep

Hay un espacio en blanco que precede a = señal. Este espacio en blanco es intercambiable con la palabra clave todos . Como habrás adivinado, esto significa que todas las capacidades disponibles en el sistema se otorgan tanto en la E efectivo y P conjuntos de capacidades permitidos.

¿Porque es esto importante? En primer lugar, los conjuntos de capacidades están vinculados a un espacio de nombres de usuario (que analizo a continuación). Por ahora, lo que esto significa es que cada espacio de nombres tendrá su propio conjunto de capacidades que solo se aplican a su propio espacio de nombres Suponga que tiene un espacio de nombres llamado restringido . Es posible que restringido miradas como si tuviera todas las capacidades adecuadas como se ve con getpcaps dominio. Sin embargo, si restringido fue creado por un proceso y un espacio de nombres que no tenía un conjunto completo de capacidades (como un usuario normal), restringido no se pueden otorgar más permisos en un sistema que el proceso de creación.

Para resumir, entonces, las capacidades, aunque no son una tecnología de espacio de nombres, funcionan de la mano para determinar qué y cómo pueden funcionar los procesos dentro de un espacio de nombres.

[ También puede disfrutar: Ejecutar Podman sin root como usuario no root ]

El espacio de nombres de usuario

Antes de sumergirse en la creación de un espacio de nombres de usuario, aquí hay un breve resumen del propósito de este espacio de nombres. Como mencioné en un artículo anterior, los nombres de usuario y, en última instancia, el número de identificación de usuario (UID), son una de las capas de seguridad que utilizará un sistema para garantizar que las personas y los procesos no accedan a cosas a las que no tienen permitido.

Teoría detrás del espacio de nombres de usuario

El espacio de nombres de usuario es una forma de que un contenedor (un conjunto de procesos aislados) tenga un conjunto de permisos diferente al del propio sistema. Cada contenedor hereda sus permisos del usuario que creó el nuevo espacio de nombres de usuario. Por ejemplo, en la mayoría de los sistemas Linux, los ID de usuario regulares comienzan en 1000 o más. Durante el resto de esta serie, utilizo un usuario llamado container-user , que tiene los siguientes ID (los contextos de SELinux se omiten para estas demostraciones):

uid=1000(container-user) gid=1000(container-user) groups=1000(container-user)

Es importante tener en cuenta que, a menos que el administrador del sistema lo restrinja intencionalmente, cualquier usuario puede, en teoría, crear un nuevo espacio de nombres de usuario. Esto, sin embargo, no proporciona ninguna ofuscación por parte de los administradores en el sistema mismo. Los espacios de nombres de usuario son una jerarquía. Considere el siguiente diagrama:

En este diagrama, las líneas negras indican el flujo de la creación. El usuario usuario-contenedor crea un espacio de nombres para un usuario llamado app-user . En teoría, esto sería una interfaz web u otra aplicación. A continuación, usuario de la aplicación crea un espacio de nombres de usuario para java-user . En este espacio de nombres, usuario-java crea un espacio de nombres para db-user .

Como se trata de una jerarquía, container-user puede ver y acceder a todos los archivos creados por cualquiera de los espacios de nombres generados desde su UID. Del mismo modo, debido a que la raíz el usuario en el sistema Linux puede ver e interactuar con todos archivos en un sistema, incluidos los creados por container-user , la raíz el usuario (representado por la línea roja) puede tener autoridad total sobre todos los espacios de nombres.

Sin embargo, lo contrario no es cierto. El usuario db el usuario, en este caso, no puede ver ni interactuar con nada por encima de él. Si la asignación de ID se mantiene igual (la política predeterminada), usuario de la aplicación , usuario de Java y db-usuario todos tienen el mismo UID. Sin embargo, aunque comparten el mismo UID, db-user no puede interactuar con java-user , que no puede interactuar con app-user , y así sucesivamente.

Cualquier permiso otorgado en un espacio de nombres de usuario solo se aplica en su propio espacio de nombres y posiblemente en los espacios de nombres debajo de él.

Práctica con espacios de nombres de usuario

Para crear un nuevo espacio de nombres de usuario, simplemente use unshare -U comando:

[container-user@localhost ~]$ PS1='\u@app-user$ ' unshare -U
nobody@app-user$

El comando anterior incluye un PS1 variable que simplemente cambia el shell para que sea más fácil determinar en qué espacio de nombres está activo el shell. Curiosamente, notará que el usuario es nadie :

nobody@app-user$ whoami
nobody
nobody@app-user$ id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)

Esto se debe a que, de forma predeterminada, no se está realizando ninguna asignación de ID de usuario. Cuando no se define una asignación, el espacio de nombres simplemente usa las reglas de su sistema para determinar cómo manejar un usuario no definido.

Sin embargo, si crea el espacio de nombres de esta manera:

PS1='\u@app-user$ ' unshare -Ur

El mapeo se creará automáticamente para usted:

root@app-user$ cat /proc/$$/uid_map
         0       1000       1

Este archivo representa lo siguiente:

ID-inside-ns      ID-outside-ns      range

La gama value representa el número de usuarios a mapear. Por ejemplo, si fuera 0 1000 4 , el mapeo sería así

0    1000
1    1001
2    1002
3    1003

Y así. La mayoría de las veces, solo te preocupas por la raíz asignación de usuarios, pero la opción está disponible si se desea. Qué sucede cuando creas el usuario Java espacio de nombres?

root@app-user$ PS1='\u@java-user$ ' unshare -Ur
root@java-user$ 

Como era de esperar, el indicador de shell cambia y usted es el usuario raíz, pero ¿cómo se ve la asignación de UID?

root@java-user$ cat /proc/$$/uid_map
         0          0          1

Ves que ahora tienes un 0 a 0 cartografía. Esto se debe a que el usuario que crea una instancia del nuevo espacio de nombres se usa para el proceso de asignación de ID. Desde que eras root en el espacio de nombres anterior, el nuevo espacio de nombres tiene una asignación de raíz para rootear . Sin embargo, dado que root en el usuario de la aplicación el espacio de nombres no tiene raíz en el sistema, tampoco el nuevo espacio de nombres root usuario.

Aparte de simplemente consultar el uid_map , también puede verificar desde fuera del espacio de nombres si dos procesos están en el mismo espacio de nombres. Por supuesto, primero debe encontrar el PID del proceso, pero con eso en la mano, puede ejecutar el siguiente comando:

readlink /proc/$PID/ns/user

Para hacerlo más fácil, ejecuté lo siguiente:

[container-user@localhost ~]$ PS1='\u@app-user$ ' unshare -Ur
root@app-user$ sleep 100000

En otra terminal, desenterré el PID y usé el readlink comando en ese PID así como en el shell actual:

[root@localhost ~]# readlink /proc/1307/ns/user 
user:[4026532275]

[root@localhost ~]# readlink /proc/$$/ns/user
user:[4026531837]

Como puede ver, el enlace de usuario es diferente. Si estuvieran operando en el mismo espacio de nombres, se vería así:

[root@localhost ~]# readlink /proc/1424/ns/user 
user:[4026532275]

[root@localhost ~]# readlink /proc/1307/ns/user 
user:[4026532275]

La mayor ventaja del espacio de nombres de usuario es la capacidad de ejecutar contenedores sin privilegios de administrador. Además, dependiendo de cómo configure la asignación de UID, puede evitar por completo tener un superusuario dentro de un espacio de nombres de usuario determinado. Esto significa que no es posible ejecutar ningún proceso privilegiado dentro de este tipo de espacio de nombres.

Nota :El usuario El espacio de nombres gobierna todos los espacios de nombres. Esto significa que las capacidades de un espacio de nombres están directamente relacionadas con las capacidades de su usuario principal. espacio de nombres.

La raíz original y completa El espacio de nombres de usuario posee todos los espacios de nombres en un sistema en el diagrama a continuación. Esta relación tiene el potencial de ser bidireccional. Si un proceso se ejecuta en la red el espacio de nombres se ejecuta como raíz , puede afectar a todos los demás procesos propiedad de la raíz espacio de nombres de usuario. Sin embargo, si bien la creación de un espacio de nombres de usuario sin privilegios permite que ese nuevo espacio de nombres de usuario acceda a recursos en otros espacios de nombres, no puede modificarlos ya que no es el propietario. Por lo tanto, mientras que un proceso en el espacio de nombres sin privilegios puede ping una IP (que se basa en la red espacio de nombres), es posible que no cambie la configuración de red del host.

Muchas cosas fuera de lo que usted considera como contenedores de Linux utilizan espacios de nombres. El formato de empaquetado de Linux Flatpak utiliza espacios de nombres de usuario, así como algunas otras tecnologías para proporcionar un entorno limitado de aplicaciones. Flatpaks agrupa todas las bibliotecas de una aplicación en el mismo archivo de distribución de paquetes. Esto permite que una máquina Linux reciba las aplicaciones más actualizadas sin tener que preocuparse si tiene la versión correcta de glibc instalado, por ejemplo. La capacidad de tener estos en su propio espacio de nombres de usuario significa que (en teoría) un proceso que se comporta mal dentro del flatpak no puede cambiar (o posiblemente incluso acceder) a ningún archivo o proceso fuera del espacio de nombres.

[ ¿Empezando con los contenedores? Consulta este curso gratuito. Implementación de aplicaciones en contenedores:una descripción técnica general. ]

Conclusión

El uso de espacios de nombres de usuario por sí solo no resuelve el problema que Flatpak y otros están tratando de manejar. Si bien los espacios de nombres de usuario son parte integral de la historia de seguridad y las capacidades de otros espacios de nombres, no brindan mucho por sí mismos. Hay mucho que considerar al crear nuevos espacios de nombres aislados. En el próximo artículo, veré cómo usar el mount espacio de nombres junto con el espacio de nombres de usuario para crear un chroot -como un entorno con espacios de nombres.

Si está buscando algunos desafíos para ayudarlo a consolidar su comprensión, intente asignar una variedad de usuarios al nuevo espacio de nombres. ¿Qué sucede si asigna todo el rango a un espacio de nombres? ¿Es posible convertirse en usuario de apache en un espacio de nombres sin privilegios? ¿Cuáles son las implicaciones de seguridad de escribir un uid_map incorrecto? ¿expediente? (Sugerencia :Necesitará dos conchas abiertas; uno para crear y vivir dentro del nuevo espacio de nombres y el otro para escribir el uid_map y gid_map archivos Si tiene problemas con esto, escríbame en Twitter @linuxovens).


Linux
  1. Cómo administrar las capacidades de archivos de Linux

  2. Espacios de nombres de Linux

  3. Linux – Kernel:¿Soporte de espacios de nombres?

  4. 50 tutoriales de administrador de sistemas UNIX/Linux

  5. Kali Linux en Android usando Linux Deploy

Escritura sin distracciones en Linux usando FocusWriter

Introducción a la gestión de contenedores de Linux

Instale MongoDB usando Vagrant en Linux

Construyendo un contenedor a mano usando espacios de nombres:El espacio de nombres de montaje

Usando el comando Watch en Linux

Usando cut en Linux Terminal