¿SELinux? Nah, dejalo en Disabled

En el mundo real muchos sysadmins y developers en sistemas operativos de la familia Red Hat deshabilitan SELinux. "¡Es muy difícil!" dicen. En Ubuntu ni en Debian existe esa cuestión ya que en su lugar se usa AppArmor. Este post no te dará servido en bandeja como solucionar un problema, sino algo mucho más valioso, que es entender el fundamento del comportamiento de SELinux y por qué funciona la solución que vayas a aplicar.

Usamos un caso práctico: tenemos una instalación basada en Laravel, php-fpm y con nginx como web server.

Entiendiendo SELinux

Tenemos una estructura de directorio así:

> tree -L 1 /home/sergio/laravel-voyager/
/home/sergio/laravel-voyager/
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── lang
├── package.json
├── phpunit.xml
├── public
├── README.md
├── resources
├── routes
├── storage
├── tests
├── vendor
└── vite.config.js

11 directories, 7 files

Veamos la información de SELinux para algunos archivos y directorios:

[sergio@masterk laravel-voyager]$ sudo ls -Zd public/storage /etc/nginx /home/sergio/.bashrc /root/.bashrc /usr/share/nginx/ bootstrap/cache/
[sudo] password for sergio: 
unconfined_u:object_r:user_home_t:s0 bootstrap/cache/      unconfined_u:object_r:user_home_t:s0 public/storage
 system_u:object_r:httpd_config_t:s0 /etc/nginx               system_u:object_r:admin_home_t:s0 /root/.bashrc
    system_u:object_r:user_home_t:s0 /home/sergio/.bashrc            system_u:object_r:usr_t:s0 /usr/share/nginx/

Si bien la salida es demasiado críptica, la podemos descifrar:

Archivo/Directorio Usuario (SELinux) Rol (SELinux) Asignación basada en tipos (SELinux) Rango (SELinux) Descripción y Observaciones
bootstrap/cache/ unconfined_u object_r user_home_t s0 Considerado como parte del directorio personal de un usuario. Debe ser httpd_sys_rw_content_t para Laravel.
public/storage unconfined_u object_r user_home_t s0 Similar a bootstrap/cache. Debe ser httpd_sys_content_t para que Laravel sirva archivos estáticos.
/etc/nginx system_u object_r httpd_config_t s0 Archivo de configuración de Nginx. Correctamente etiquetado como httpd_config_t. No requiere cambios.
/home/sergio/.bashrc system_u object_r user_home_t s0 Archivo personal de configuración de un usuario. Etiquetado como user_home_t, lo cual es correcto.
/root/.bashrc system_u object_r admin_home_t s0 Archivo de configuración del usuario root. Etiquetado como admin_home_t, lo cual es correcto.
/usr/share/nginx/ system_u object_r usr_t s0 Etiquetado como usr_t, que es genérico. Debe ser httpd_sys_content_t si Nginx sirve contenido estático aquí.

Descripción de los campos

Concepto Descripción Ejemplo
Usuario (SELinux) Define el usuario de SELinux asociado al recurso. system_u: Archivo gestionado por el sistema.
unconfined_u: Archivo de un usuario sin restricciones.
Rol (SELinux) Define el rol asignado al recurso. Usualmente es object_r para archivos regulares y directorios.
Asignación basada en tipos Determina qué procesos o aplicaciones pueden interactuar con un recurso. httpd_sys_rw_content_t: Permite lectura/escritura por parte del servidor web.
httpd_sys_content_t: Permite solo lectura por parte del servidor web.
user_home_t: Para directorios o archivos en /home.
Rango (SELinux) Se usa para configuraciones de seguridad MLS o TE (usualmente s0 en configuraciones estándar). Normalmente no requiere ajustes en configuraciones básicas.

Ahora bien, hay varios tipos de políticas en cuanto a funcionalidades que se pueden aplicar en SELinux, la que se usa en general es targeted. El tipo de política targeted significa que determinados servicios se ejecutan de manera confinada, es decir limitada para realizar solamente determinadas acciones más allá de los permisos ordinarios.

Y es importante enfatizar que cuando usamos targeted el campo que más nos tiene que interesar de un contexto es el "Type Enforcement". En general no prestaremos demasiada atención al resto de los campos.

Lo más importante para entender es que las reglas de SELinux tienen sujetos y objetos. El contexto o etiqueta del objeto podría no permitir que el sujeto realice una determinada acción al sujeto, y es allí cuando se produce un error. Dicho error tiene por objetivo proteger al sistema. En este ejemplo los sujetos son nginx y php-fpm y los objetos son los directorios bootstrap/cache y public/storage (y sus respectivos contenidos).

Por ejemplo, nginx sería el sujeto y con el siguiente comando vemos el contexto del proceso:

> ps -C nginx,php-fpm -Z
LABEL                               PID TTY          TIME CMD
system_u:system_r:httpd_t:s0        898 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1118 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1119 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1120 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1121 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1122 ?        00:00:00 php-fpm
system_u:system_r:httpd_t:s0       1451 ?        00:00:00 nginx
system_u:system_r:httpd_t:s0       1452 ?        00:00:00 nginx
system_u:system_r:httpd_t:s0       1453 ?        00:00:00 nginx

Pero habrá problemas...

La cuestión es que tanto nginx como php-fpm se ejecutan con la asignación de tipo (TE) httpd_t y la TE que tienen directorior como bootstrap/cache y public/storage es user_home_t.

Con el siguiente comando podemos verifucar qué cosas puede hacer el dominio httpd_t en el contexto del objeto que tiene el tipo de asignación user_home_t:

> sesearch -s httpd_t -A -t user_home_t
allow daemon user_home_t:file { append getattr };
allow domain file_type:blk_file map; [ domain_can_mmap_files ]:True
allow domain file_type:chr_file map; [ domain_can_mmap_files ]:True
allow domain file_type:file map; [ domain_can_mmap_files ]:True
allow domain file_type:lnk_file map; [ domain_can_mmap_files ]:True
allow httpd_t file_type:dir { getattr open search };
allow httpd_t file_type:filesystem getattr;
allow httpd_t user_home_t:file map; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:dir { getattr ioctl lock open read search }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:dir { getattr open search }; [ httpd_enable_homedirs ]:True
allow httpd_t user_home_type:dir { getattr open search }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:dir { getattr open search }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:lnk_file { getattr read }; [ httpd_enable_homedirs ]:True

Cada regla tiene:

  • El tipo de regla ( allow )
  • El sujeto de la regla ( httpd_t )
  • El contexto y tipo del objeto ( user_home_t:file )
  • Los permisos (append y getattr)

¡No hay ninguna regla que permita la modificación total del contenido del archivo ni la creación de archivos en la carpeta (por ejemplo, el permiso write)!. Esto significa que, de manera predeterminada, servicios como apache o nginx no tienen permiso para escribir en los directorios home de los usuarios. Y por ese motivo hay cosas que fallarán, sin importar los permisos ni el usuario...

Por lo tanto los directorios public/storage y bootstrap/cache y su contenido necesitan otro contexto para que Laravel funcione correctamente cuando está ubicado en un subdirectorio de /home. Mirando la documentación podemos ver que httpd_sys_content_t y httpd_sys_rw_content son respectivamente los apropiados para esos directorios y su contenido:

httpd_sys_content_t

- Set files with the httpd_sys_content_t type, if you want to treat the files as httpd sys content.

Paths:
     /srv/([^/]*/)?www(/.*)?,  /var/www(/.*)?,  /etc/htdig(/.*)?,   /srv/gallery2(/.*)?,   /var/lib/trac(/.*)?,   /var/lib/htdig(/.*)?,
     /var/www/icons(/.*)?,     /usr/share/glpi(/.*)?,     /usr/share/htdig(/.*)?,     /usr/share/drupal.*,     /usr/share/z-push(/.*)?,
     /var/www/svn/conf(/.*)?,         /usr/share/icecast(/.*)?,          /var/lib/cacti/rra(/.*)?,          /usr/share/ntop/html(/.*)?,
     /usr/share/nginx/html(/.*)?,      /usr/share/doc/ghc/html(/.*)?,      /usr/share/openca/htdocs(/.*)?,      /usr/share/selinux-pol
     icy[^/]*/html(/.*)?

y

httpd_sys_rw_content_t

- Set files with the httpd_sys_rw_content_t type, if you want to treat the files as httpd sys read/write content.

Paths:
     /etc/rt(/.*)?,  /etc/glpi(/.*)?,  /etc/horde(/.*)?,  /etc/drupal.*,  /etc/z-push(/.*)?,  /var/lib/svn(/.*)?,   /var/www/svn(/.*)?,
     /etc/owncloud(/.*)?,    /var/www/html(/.*)?/uploads(/.*)?,    /var/www/html(/.*)?/wp-content(/.*)?,   /var/www/html(/.*)?/wp_back
     ups(/.*)?,  /var/www/html(/.*)?/sites/default/files(/.*)?,  /var/www/html(/.*)?/sites/default/settings.php,  /etc/mock/koji(/.*)?,
     /etc/nextcloud(/.*)?,       /var/lib/drupal.*,      /etc/zabbix/web(/.*)?,      /var/lib/moodle(/.*)?,      /var/log/z-push(/.*)?,
     /var/spool/gosa(/.*)?,   /etc/WebCalendar(/.*)?,   /usr/share/joomla(/.*)?,   /var/lib/dokuwiki(/.*)?,    /var/lib/owncloud(/.*)?,
     /var/spool/viewvc(/.*)?, /var/lib/nextcloud(/.*)?, /var/lib/pootle/po(/.*)?, /var/lib/phpMyAdmin(/.*)?, /var/www/moodledata(/.*)?,
     /srv/gallery2/smarty(/.*)?,      /var/www/moodle/data(/.*)?,      /var/lib/graphite-web(/.*)?,      /var/log/shibboleth-www(/.*)?,
     /var/www/gallery/albums(/.*)?,  /var/www/html/owncloud/data(/.*)?, /var/www/html/nextcloud/data(/.*)?, /usr/share/wordpress-mu/wp-
     content(/.*)?, /usr/share/wordpress/wp-content/upgrade(/.*)?, /usr/share/wordpress/wp-content/uploads(/.*)?, /var/www/html/config
     uration.php

De modo que esos son los contextos que tendrías que aplicar a esos directorios y sus contenidos. El resto, si sos administrador o desarrollador de Linux con algo de experiencia es anecdótico.

Fuentes y más recursos

Comentarios

Comments powered by Disqus