GNU/Linux >> Tutoriales Linux >  >> Linux

Uso de las funciones de systemd para proteger los servicios

Todas las versiones recientes de las distribuciones de Linux más populares utilizan systemd para iniciar la máquina y administrar los servicios del sistema. Systemd proporciona varias funciones para facilitar el inicio de los servicios y más seguro. Esta es una combinación rara, y este artículo muestra por qué es útil dejar que systemd administrar los recursos y el sandboxing de un servicio.

Justificación

Entonces, ¿por qué deberíamos usar systemd para el sandboxing de seguridad? En primer lugar, se podría argumentar que cada parte de esta funcionalidad ya está expuesta a través de herramientas conocidas y existentes, que se pueden programar y combinar de forma arbitraria. En segundo lugar, particularmente en el caso de programas escritos en C/C++ y otros lenguajes de bajo nivel, las llamadas al sistema apropiadas pueden usarse directamente, logrando una implementación ajustada cuidadosamente adaptada a las necesidades de un servicio en particular.

Hay cuatro razones principales:

1. La seguridad es difícil. Una implementación centralizada en el administrador de servicios significa que un servicio que lo aprovecha puede simplificarse significativamente. Sin duda, esta implementación centralizada es compleja, pero debido a su amplio uso, está bien probada. Si consideramos que se reutiliza en miles de servicios, el global se reduce la complejidad del sistema.

2. Las primitivas de seguridad varían entre sistemas. Systemd suaviza las diferencias entre las arquitecturas de hardware, las versiones del kernel y las configuraciones del sistema.

La funcionalidad que proporciona el fortalecimiento de los servicios se implementa en la medida de lo posible en un sistema determinado. Por ejemplo, un systemd La unidad puede contener configuraciones AppArmor y SELinux. El primero se usa en sistemas Ubuntu/Debian, el segundo en Fedora/RHEL/CentoOS, y tampoco para distribuciones que no habilitan ningún sistema MAC. La otra cara de esta flexibilidad es que no se puede confiar en esas funciones como únicas mecanismo de contención (o que dichos servicios solo se usan en sistemas que admiten todas las funciones requeridas).

3. La seguridad requiere manipulación de bajo nivel con el sistema. Las funciones proporcionadas por el administrador de servicios son independientes del lenguaje de implementación del servicio, por lo que es fácil escribir un servicio en un lenguaje de alto nivel, por ejemplo, shell o Python o lo que sea conveniente, y aun así bloquearlo.

4. La seguridad requiere privilegios. Esto es una paradoja, pero se requieren privilegios para quitar privilegios. Por ejemplo, a menudo necesitamos ser root para configurar un espacio de nombres de montaje personalizado para limitar una vista del sistema de archivos. Como otro ejemplo, un demonio HTTP a menudo se inicia como root solo para poder abrir un puerto con un número bajo y los puertos con un número bajo se restringen en nombre de la seguridad. El administrador de servicios debe ejecutarse con los privilegios más altos de todos modos, pero los servicios no deberían, y la configuración de refuerzo suele ser la única razón para requerir privilegios más altos. Cualquier error en la implementación del servicio en esta fase puede ser peligroso. Al descargar la configuración al administrador de servicios, los servicios pueden comenzar sin esta fase inicial de privilegios elevados.

Para poner esto en contexto, el Fedora 32 lanzado recientemente contiene casi 1800 archivos de unidades diferentes para iniciar servicios escritos en C, C++, Python, Java, Ocaml, Perl, Ruby, Lua, Tcl, Erlang y así sucesivamente, y solo un systemd .

[ ¿Necesita más información sobre systemd? Descargue la hoja de trucos de systemd para obtener más consejos útiles. ]

Algunas formas equivalentes de iniciar un servicio

Más comúnmente, systemd los servicios se definen a través de un archivo de unidad :un archivo de texto en formato ini que declara los comandos a ejecutar y varias configuraciones. Después de editar este archivo de unidad, systemctl daemon-reload debe llamarse para empujar al administrador para que cargue la nueva configuración. La salida del daemon aterriza en el diario y se usa un comando separado para verla. Cuando se ejecutan comandos de forma interactiva, todo eso no es muy conveniente. El systemd-run El comando le dice al administrador que inicie un comando en nombre del usuario y es una excelente alternativa para el uso interactivo. El comando a ejecutar se especifica de manera similar a sudo . El primer argumento posicional y todo lo que sigue es el comando real, y cualquier opción anterior es interpretada por systemd-run sí mismo. El systemd-run El comando tiene opciones para especificar configuraciones específicas como --uid y --gid para el usuario y el grupo. El -E opción establece una variable de entorno, mientras que una opción "catch-all" -p acepta pares clave=valor arbitrarios similares al archivo de unidad.

$ systemd-run whoami
Running as unit: run-rbd26afbc67d74371a6d625db78e33acc.service
$ journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
-- Logs begin at Thu 2020-04-23 19:31:49 CEST, end at Mon 2020-04-27 13:22:35 CEST. --
Apr 27 13:22:18 fedora systemd[1]: Started run-rbd26afbc67d74371a6d625db78e33acc.service.
Apr 27 13:22:18 fedora whoami[520662]: root
Apr 27 13:22:18 fedora systemd[1]: run-rbd26afbc67d74371a6d625db78e33acc.service: Succeeded.

systemd-run -t conecta los flujos estándar de entrada, salida y error del comando al terminal que lo invoca. Esto es excelente para ejecutar comandos de forma interactiva (tenga en cuenta que el proceso de servicio sigue siendo un elemento secundario del administrador).

$ systemd-run -t whoami
Running as unit: run-u53517.service
Press ^] three times within 1s to disconnect TTY.
root

Entorno coherente

Una unidad siempre comienza en un entorno cuidadosamente definido. Cuando comenzamos una unidad usando systemctl o systemd-run , el comando siempre se invoca como un elemento secundario del administrador. El entorno del shell no afecta el entorno en el que se ejecutan los comandos de servicio. No todas las configuraciones que se pueden especificar en un archivo de unidad son compatibles con systemd-run , pero la mayoría lo son, y siempre que nos atengamos a ese subconjunto, la invocación a través de un archivo de unidad y systemd-run son equivalentes. De hecho, systemd-run crea un archivo de unidad temporal sobre la marcha.

Por ejemplo:

$ sudo systemd-run -M rawhide -t /usr/bin/grep PRETTY_NAME= /etc/os-release

Aquí, sudo habla con PAM para permitir la escalada de privilegios y luego ejecuta systemd-run como raíz. A continuación, systemd-run establece una conexión con una máquina llamada rawhide , donde habla con el administrador del sistema (PID 1 en el contenedor) a través de dbus. El administrador invoca grep , que hace su trabajo. El grep el comando se imprime en stdout, que está conectado a la pseudo-terminal desde la cual sudo fue invocado.

Configuración de seguridad

Usuarios y usuarios dinámicos

Sin más preámbulos, hablemos de algunas configuraciones específicas, comenzando con las primitivas más simples y poderosas.

Primero, el mecanismo de separación de privilegios más antiguo, más básico y posiblemente más útil:los usuarios. Puede definir usuarios con User=foobar en el [Servicio] sección de un archivo de unidad, o systemd-run -p User=foobar o systemd-run --uid=foobar . Puede parecer obvio, y en Android, cada aplicación tiene su propio usuario, pero en el mundo de Linux, todavía tenemos demasiados servicios que se ejecutan innecesariamente como root.

Systemd proporciona un mecanismo para crear usuarios bajo demanda. Cuando se invoca con DynamicUser=yes , se asigna un número de usuario único para el servicio. Este número se resuelve en un nombre de usuario temporal. Esta asignación no se almacena en /etc/passwd , sino que un módulo NSS lo genera sobre la marcha cada vez que se consulta el número o el nombre correspondiente. Después de que se detenga el servicio, el número podría reutilizarse más tarde para otro servicio.

¿Cuándo se debe usar un usuario estático normal para un servicio y cuándo se prefiere uno dinámico? Los usuarios dinámicos son excelentes cuando la identidad del usuario es efímera y no se necesita integración con otros servicios en el sistema. Pero cuando tenemos una política en la base de datos para permitir el acceso de usuarios específicos, directorios compartidos con un grupo en particular o cualquier otra configuración en la que queramos referirnos al nombre de usuario, los usuarios dinámicos probablemente no sean la mejor opción.

Montar espacios de nombres

En general, se debe tener en cuenta que systemd a menudo es solo la funcionalidad de envoltura proporcionada por el kernel. Por ejemplo, varias configuraciones que limitan el acceso al árbol del sistema de archivos, haciendo que partes de él sean de solo lectura o inaccesibles, se logran organizando los sistemas de archivos apropiados en un espacio de nombres de montaje no compartido.

Varias configuraciones útiles se implementan de esta manera. Los dos más útiles y generales son ProtectHome= y ProtectSystem= . El primero usa un espacio de nombres de montaje no compartido para hacer /home ya sea de solo lectura o completamente inaccesible. El segundo se trata de proteger /usr , /boot y /etc .

Una tercera configuración también útil pero muy específica es PrivateTmp= . Utiliza espacios de nombres de montaje para hacer visible un directorio privado como /tmp y /var/tmp por el servicio Los archivos temporales del servicio están ocultos para otros usuarios para evitar problemas debido a colisiones de nombres de archivos o permisos incorrectos.

La vista del sistema de archivos se puede administrar a nivel de directorios individuales a través de InaccessiblePaths= , ReadOnlyPaths= , ReadWritePaths= , BindPaths= y ReadOnlyBindPaths= . Las dos primeras configuraciones brindan acceso total o solo de escritura a partes de una jerarquía del sistema de archivos. El tercero se trata de restaurar el acceso, lo cual es útil cuando queremos dar acceso completo solo a algún directorio específico en lo profundo de la jerarquía. Los dos últimos permiten mover directorios o, más precisamente, montarlos de forma privada en una ubicación diferente.

Volviendo al asunto de DynamicUser=yes , estos usuarios transitorios solo son posibles cuando el servicio no puede crear archivos permanentes en el disco. Si dichos archivos fueran visibles para otros usuarios, se mostraría que no tienen propietario o, lo que es peor, el nuevo usuario transitorio con el mismo número podría acceder a ellos, lo que provocaría una fuga de información o una escalada de privilegios involuntaria. Systemd utiliza espacios de nombres de montaje para hacer que la mayor parte del árbol del sistema de archivos no se pueda escribir en el servicio. Para permitir el almacenamiento permanente, se monta un directorio privado en el árbol del sistema de archivos visible para el servicio.

Tenga en cuenta que esas protecciones son independientes del mecanismo básico de control de acceso a archivos mediante la propiedad de archivos y la máscara de permisos. Si un sistema de archivos se monta como de solo lectura, incluso los usuarios que podrían modificar archivos específicos en función de los permisos estándar no podrán hacerlo hasta que el sistema de archivos se vuelva a montar en lectura y escritura. Esto proporciona una protección contra errores en la gestión de archivos (después de todo, no es raro que los usuarios configuren la máscara de permiso incorrecta de vez en cuando) y es una capa de una estrategia de defensa en profundidad.

Los recursos implementados usando espacios de nombres de montaje generalmente son muy eficientes porque la implementación del kernel es eficiente. Los gastos generales al configurarlos también suelen ser insignificantes.

Creación automática de directorios para un servicio

Una función relativamente nueva que systemd proporciona servicios es la gestión automática de directorios. Las diferentes rutas del sistema de archivos tienen diferentes características de almacenamiento y usos previstos, pero se dividen en algunas categorías estándar. El FHS especifica que /etc es para archivos de configuración, /var/cache es para almacenamiento no permanente, /var/lib/ es para almacenamiento semipermanente, /var/log para los registros y /run para archivos volátiles. Un servicio a menudo necesita un subdirectorio en cada una de esas ubicaciones. Systemd configura eso automáticamente, controlado por ConfigurationDirectory= , CacheDirectory= , StateDirectory= , LogsDirectory=RuntimeDirectory= ajustes. El usuario posee esos directorios. El administrador elimina el directorio de tiempo de ejecución cuando se detiene el servicio. La idea general es vincular la existencia de esos activos del sistema de archivos a la vida útil del servicio. No es necesario crearlos de antemano y se limpian adecuadamente una vez que el servicio deja de ejecutarse.

$ sudo systemd-run -t -p User=user -p CacheDirectory=foo -p StateDirectory=foo -p RuntimeDirectory=foo -p PrivateTmp=yes ls -ld /run/foo /var/cache/foo /var/lib/foo /etc/foo /tmp/
Running as unit: run-u45882.service
Press ^] three times within 1s to disconnect TTY.
drwxr-xr-x  2 user    user   40 Apr 26 08:21 /run/foo           ← automatically created and removed
drwxr-xr-x  2 user    user 4096 Apr 26 08:20 /var/cache/foo     ← automatically created
drwxr-xr-x. 2 user    user 4096 Nov 13 21:50 /var/lib/foo       ← automatically created
drwxr-xr-x. 2 root    root    4096 Nov 13 21:50 /etc/foo           ← automatically created, but not owned by the user, since the service (usually) shall not modify its own configuration
drwxrwxrwt  2 root    root      40 Apr 26 08:21 /tmp/              ← "sticky bit" is set, but this directory is not the one everyone else sees

Por supuesto, esas siete ubicaciones (contando PrivateTmp= como dos) no cubren las necesidades de todos los servicios, pero deberían ser suficientes para la mayoría de las situaciones. Para otros casos, configuración manual o una configuración adecuada en tmpfiles.d siempre es una opción.

La gestión automática de directorios se relaciona muy bien con DynamicUser= configuración y usuarios creados automáticamente, al proporcionar un servicio que se ejecuta como un usuario independiente y no se le permite modificar la mayor parte del árbol del sistema de archivos (incluso si los permisos de acceso a los archivos lo permiten). El servicio aún puede acceder a directorios seleccionados y almacenar datos allí, sin ninguna configuración que no sea la configuración del archivo de la unidad.

Por ejemplo, un servicio web de Python podría ejecutarse como:

$ systemd-run -p DynamicUser=yes -p ProtectHome=yes -p StateDirectory=webserver --working-directory=/srv/www/content python3 -m http.server 8000

o a través del archivo de unidad equivalente:

[Service]
DynamicUser=yes
ProtectHome=yes
StateDirectory=webserver
WorkingDirectory=/srv/www/content
ExecStart=python3 -m http.server 8000

Nos aseguramos de que el servicio se ejecute como un usuario transitorio sin la capacidad de modificar el sistema de archivos o tener acceso a los datos del usuario.

Los ajustes descritos aquí se pueden considerar de "alto nivel". Aunque la implementación puede ser complicada, los conceptos en sí mismos se entienden fácilmente y el efecto en el servicio es claro. Hay una gran cantidad de otras configuraciones para eliminar varios permisos y capacidades, bloquear protocolos de red y parámetros ajustables del kernel, e incluso deshabilitar llamadas de sistema individuales. Estos están fuera del alcance de este breve artículo. Consulte la extensa documentación de referencia.

Poniendo todo esto en uso

Cuando tenemos una buena comprensión de lo que hace y necesita el servicio, podemos considerar qué privilegios se requieren y qué podemos eliminar. Los candidatos obvios se postulan como usuarios sin privilegios y limitan el acceso a los datos del usuario en /home . Cuanto más permitamos systemd para configurar las cosas para nosotros (por ejemplo, usando StateDirectory= y amigos), es más probable que el servicio pueda ejecutarse con éxito como un usuario sin privilegios. A menudo, el servicio necesita acceso a un subdirectorio específico, y podemos lograrlo usando ReadWritePaths= y configuraciones similares.

Agregar medidas de seguridad de forma automática es imposible. Sin una buena comprensión de lo que necesita el servicio en diferentes escenarios de configuración y para diferentes operaciones, no podemos definir un sandbox útil. Esto significa que el sandboxing de los servicios lo realizan mejor sus autores o mantenedores.

Evaluación y statu quo

La cantidad de configuraciones posibles es grande y se agregan nuevas con cada versión de systemd . Mantenerse al día con eso es difícil. Systemd proporciona una herramienta para evaluar el uso de directivas de sandboxing en el archivo de la unidad. Los resultados deben considerarse sugerencias:después de todo, como se mencionó anteriormente, la creación automática de una política de seguridad es difícil, y cualquier evaluación es solo contar lo que se usa y lo que no, sin una comprensión profunda de lo que importa para un servicio determinado.

$ systemd-analyze security systemd-resolved.service
  NAME                    DESCRIPTION                                                       EXPOSURE
...
✓ User=/DynamicUser=      Service runs under a static non-root user identity                       
✗ DeviceAllow=            Service has a device ACL with some special devices                0.1
✓ PrivateDevices=         Service has no access to hardware devices                                
✓ PrivateMounts=          Service cannot install system mounts                                     
  PrivateTmp=             Service runs in special boot phase, option does not apply                
✗ PrivateUsers=           Service has access to other users                                 0.2
  ProtectHome=            Service runs in special boot phase, option does not apply                
✓ ProtectKernelLogs=      Service cannot read from or write to the kernel log ring buffer          
✓ ProtectKernelModules=   Service cannot load or read kernel modules                               
✓ ProtectKernelTunables=  Service cannot alter kernel tunables (/proc/sys, …)                      
  ProtectSystem=          Service runs in special boot phase, option does not apply                
✓ SupplementaryGroups=    Service has no supplementary groups                                      
...

→ Overall exposure level for systemd-resolved.service: 2.1 OK 🙂

$ systemd-analyze security httpd.service
  NAME                    DESCRIPTION                                                        EXPOSURE
...
✗ User=/DynamicUser=      Service runs as root user                                          0.4
✗ DeviceAllow=            Service has no device ACL                                          0.2
✗ PrivateDevices=         Service potentially has access to hardware devices                 0.2
✓ PrivateMounts=          Service cannot install system mounts                                  
✓ PrivateTmp=             Service has no access to other software's temporary files             
✗ PrivateUsers=           Service has access to other users                                  0.2
✗ ProtectHome=            Service has full access to home directories                        0.2
✗ ProtectKernelLogs=      Service may read from or write to the kernel log ring buffer       0.2
✗ ProtectKernelModules=   Service may load or read kernel modules                            0.2
✗ ProtectKernelTunables=  Service may alter kernel tunables                                  0.2
✗ ProtectSystem=          Service has full access to the OS file hierarchy                   0.2
  SupplementaryGroups=    Service runs as root, option does not matter                          
...

→ Overall exposure level for httpd.service: 9.2 UNSAFE 😨

Nuevamente, esto no significa que el servicio sea inseguro, sino que no está utilizando systemd primitivas de seguridad.

Mirando el nivel de toda la distribución:

$ systemd-analyze security '*'

Vemos que la mayoría de los servicios obtienen una puntuación muy alta (es decir, mala). No podemos recopilar tales estadísticas sobre varios servicios internos, pero parece razonable suponer que son similares. Sin duda, hay muchas cosas al alcance de la mano, y la aplicación de un sandboxing relativamente simple haría que nuestros sistemas fueran más seguros.

Terminar

Permitiendo que systemd administrar servicios y sandboxing puede ser una excelente manera de agregar una capa de seguridad a sus servidores Linux. Considere probar las configuraciones anteriores para ver qué podría beneficiar a su organización.

En este artículo, evitamos cuidadosamente cualquier mención a la creación de redes. Esto se debe a que la segunda entrega hablará sobre la activación de sockets y el sandboxing de los servicios que utilizan la red.

[ No olvide consultar la hoja de trucos de systemd para obtener más sugerencias útiles. ]


Linux
  1. Agregar un nuevo servicio a Linux systemd

  2. Comprobar los servicios en ejecución en Linux

  3. Cómo detener el servicio systemd

  4. Usando la variable en la ruta de comando para ExecStart en el servicio systemd

  5. Cómo eliminar los servicios de systemd

Comandos Systemctl para administrar el servicio Systemd

Administrar cgroups con systemd

Cómo configurar la ejecución automática de un script de Python usando Systemd

Servicios de red

Forma correcta de usar Ubuntu systemctl para controlar Systemd

Systemd:Uso de After y Requires