SELinux (Security-Enhanced Linux) es el sistema de control de acceso obligatorio (MAC) que viene activado por defecto en Rocky Linux, AlmaLinux y Red Hat Enterprise Linux. Además de los permisos clásicos de usuario y grupo, el kernel comprueba que cada proceso solo acceda a los archivos, puertos y recursos que su política permite. En este tutorial aprenderás a consultar y cambiar el modo de SELinux en Rocky Linux 9 y a resolver los tres problemas más habituales al publicar un servicio: contextos de archivo incorrectos, puertos no permitidos y booleanos desactivados. Usarás Nginx como ejemplo práctico.
Requisitos previos
- Un servidor con Rocky Linux 9 (o AlmaLinux 9 / RHEL 9), por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Acceso a la consola del servidor por si necesitas reiniciar con un cambio de modo.
NotaUbuntu y Debian usan AppArmor en lugar de SELinux. Si trabajas con Ubuntu 24.04, consulta la guía de configuración de perfiles AppArmor.
Conceptos básicos
Antes de tocar nada conviene tener claros tres conceptos:
- Contexto: cada proceso, archivo y puerto tiene una etiqueta con el formato
usuario:rol:tipo:nivel. En la políticatargetedque usa Rocky Linux, lo que importa casi siempre es el tipo, por ejemplohttpd_tpara el proceso de Nginx ohttpd_sys_content_tpara los archivos web. - Política: reglas que dicen qué tipo de proceso puede hacer qué sobre qué tipo de objeto. Todo lo que no está permitido explícitamente se deniega.
- Modo: determina si las denegaciones se aplican o solo se registran.
| Modo | Qué hace | Cuándo usarlo |
|---|---|---|
enforcing | Aplica la política y registra las denegaciones | Producción, siempre que sea posible |
permissive | No bloquea nada, pero registra lo que habría bloqueado | Diagnóstico temporal |
disabled | No carga ninguna política | No recomendado |
Paso 1: Comprobar el estado de SELinux
Instala primero las herramientas de administración. policycoreutils-python-utils incluye semanage y audit2allow, y setroubleshoot-server añade sealert, que traduce las denegaciones a explicaciones legibles:
sudo dnf install policycoreutils-python-utils setroubleshoot-server
Consulta el modo actual con getenforce:
getenforce
Enforcing
Para ver el detalle completo usa sestatus:
sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
Current mode es el modo en ejecución y Mode from config file el que se aplicará en el siguiente arranque. Si no coinciden, alguien ha cambiado el modo en caliente.
También puedes ver el contexto de tu usuario, de los procesos y de los archivos con la opción -Z, que aceptan muchos comandos:
id -Z
ls -Z /etc/passwd
ps -eZ | grep sshd
Paso 2: Cambiar entre enforcing y permissive
El modo se puede cambiar en caliente, sin reiniciar, con setenforce. Este cambio se pierde en el siguiente arranque:
sudo setenforce 0 # permissive
getenforce
sudo setenforce 1 # enforcing
getenforce
Para que el cambio sea permanente, edita el archivo de configuración:
sudo nano /etc/selinux/config
Las dos directivas relevantes son estas:
SELINUX=enforcing
SELINUXTYPE=targeted
SELINUX acepta enforcing, permissive o disabled, y SELINUXTYPE debe quedarse en targeted salvo que tengas un motivo muy concreto.
En lugar de poner todo el sistema en permissive para diagnosticar un servicio, puedes poner solo su dominio en modo permisivo y dejar el resto protegido:
sudo semanage permissive -a httpd_t
Cuando termines, vuelve a activarlo:
sudo semanage permissive -d httpd_t
Volver a activar SELinux si estaba desactivado
Si heredas un servidor con SELinux desactivado, no pases directamente a enforcing: los archivos creados mientras estaba desactivado no tienen etiqueta y muchos servicios fallarían al arrancar. Sigue este orden.
En Rocky Linux 9 la forma soportada de desactivar SELinux es el parámetro de kernel selinux=0, así que elimínalo por si existe:
sudo grubby --update-kernel ALL --remove-args selinux
Pon SELINUX=permissive en /etc/selinux/config, marca el sistema para reetiquetar todos los archivos en el próximo arranque y reinicia:
sudo touch /.autorelabel
sudo reboot
El reetiquetado puede tardar varios minutos en discos con muchos archivos. Después, revisa las denegaciones registradas (paso 6) y, cuando no aparezcan nuevas, cambia a SELINUX=enforcing y ejecuta sudo setenforce 1.
Paso 3: Corregir contextos de archivos
El caso más frecuente: sirves una web desde un directorio que no es el predeterminado y Nginx devuelve 403 Forbidden aunque los permisos Unix son correctos. Vamos a reproducirlo.
Instala Nginx, crea un directorio web en /srv/web y una página de prueba:
sudo dnf install nginx
sudo mkdir -p /srv/web
echo "Hola desde /srv/web" | sudo tee /srv/web/index.html
Crea un bloque de servidor que use ese directorio en el puerto 80:
sudo nano /etc/nginx/conf.d/web.conf
server {
listen 80;
server_name your_domain;
root /srv/web;
}
Sustituye your_domain por tu dominio o por la IP del servidor. Comprueba la configuración, arranca Nginx y abre el puerto HTTP en firewalld:
sudo nginx -t
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
Haz una petición local indicando el nombre del servidor:
curl -i -H "Host: your_domain" http://127.0.0.1/
HTTP/1.1 403 Forbidden
Mira el contexto de los archivos y compáralo con el del directorio web por defecto:
ls -Zd /srv/web /srv/web/index.html /usr/share/nginx/html
unconfined_u:object_r:var_t:s0 /srv/web
unconfined_u:object_r:var_t:s0 /srv/web/index.html
system_u:object_r:httpd_sys_content_t:s0 /usr/share/nginx/html
El proceso httpd_t puede leer httpd_sys_content_t, pero no var_t. La solución correcta es registrar una regla de contexto permanente con semanage fcontext y aplicarla con restorecon:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
sudo restorecon -Rv /srv/web
Relabeled /srv/web from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
Relabeled /srv/web/index.html from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
Repite la petición:
curl -H "Host: your_domain" http://127.0.0.1/
Hola desde /srv/web
Si la aplicación necesita escribir en un subdirectorio (subidas, caché), usa el tipo httpd_sys_rw_content_t solo para ese subdirectorio, nunca para todo el sitio.
Importante
chcontambién cambia contextos, pero el cambio se pierde en el siguienterestorecono reetiquetado completo. Úsalo solo para pruebas. Ten en cuenta además quemvconserva el contexto de origen, mientras quecpaplica el del directorio de destino: si mueves archivos desde tu home, ejecutarestorecondespués.
Puedes listar las reglas personalizadas que has añadido con:
sudo semanage fcontext -l -C
Paso 4: Permitir un puerto no estándar
SELinux también etiqueta los puertos. Nginx (httpd_t) solo puede escuchar en los puertos del tipo http_port_t y relacionados. Consulta cuáles son:
sudo semanage port -l | grep -w http_port_t
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
Cambia listen 80; por listen 8090; en /etc/nginx/conf.d/web.conf y reinicia Nginx:
sudo systemctl restart nginx
El reinicio falla. En el journal verás el motivo:
sudo journalctl -u nginx -n 20 --no-pager
nginx: [emerg] bind() to 0.0.0.0:8090 failed (13: Permission denied)
Añade el puerto al tipo http_port_t:
sudo semanage port -a -t http_port_t -p tcp 8090
Si semanage responde que el puerto ya está definido para otro tipo, usa -m (modificar) en lugar de -a. Reinicia Nginx y abre el puerto en el firewall:
sudo systemctl restart nginx
sudo firewall-cmd --permanent --add-port=8090/tcp
sudo firewall-cmd --reload
curl -H "Host: your_domain" http://127.0.0.1:8090/
Hola desde /srv/web
Paso 5: Gestionar booleanos
Los booleanos activan o desactivan partes de la política sin escribir reglas. Por ejemplo, por defecto Nginx no puede abrir conexiones de red salientes, así que un proxy_pass hacia una aplicación en 127.0.0.1:3000 devuelve 502 Bad Gateway y en el log de Nginx aparece connect() ... failed (13: Permission denied).
Lista los booleanos relacionados con el servidor web:
getsebool -a | grep httpd
httpd_can_network_connect --> off
httpd_can_network_connect_db --> off
httpd_can_sendmail --> off
...
semanage boolean -l muestra además una descripción de cada uno:
sudo semanage boolean -l | grep httpd_can_network_connect
Activa el booleano de forma permanente con -P (sin esa opción se pierde al reiniciar):
sudo setsebool -P httpd_can_network_connect on
getsebool httpd_can_network_connect
httpd_can_network_connect --> on
Si Nginx solo tiene que conectar con una base de datos, httpd_can_network_connect_db es más restrictivo y preferible. Activa únicamente los booleanos que tu aplicación necesita.
Paso 6: Diagnosticar denegaciones
Cada denegación queda registrada como un mensaje AVC en /var/log/audit/audit.log. Para ver las recientes:
sudo ausearch -m AVC -ts recent
type=AVC msg=audit(1727251200.123:456): avc: denied { name_bind } for pid=2345 comm="nginx" src=8090 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
Los campos clave son el permiso denegado (name_bind), el proceso (comm), el tipo del proceso (scontext) y el tipo del objeto (tcontext).
sealert analiza el log y propone soluciones concretas, normalmente un semanage o un setsebool:
sudo sealert -a /var/log/audit/audit.log
audit2why explica por qué se produjo cada denegación:
sudo ausearch -m AVC -ts recent | audit2why
Algunas denegaciones no se registran porque la política las marca como dontaudit. Si no ves nada y sospechas de SELinux, desactiva esas reglas temporalmente, reproduce el problema y vuelve a activarlas:
sudo semodule -DB
sudo semodule -B
Paso 7: Crear un módulo de política propio (último recurso)
Si ni un contexto, ni un puerto, ni un booleano resuelven el problema, puedes generar un módulo local a partir de las denegaciones con audit2allow. Revisa siempre lo que genera antes de cargarlo, porque permite exactamente lo que se denegó, incluidos accesos que quizá no deberían permitirse:
sudo ausearch -m AVC -c nginx --raw | audit2allow -M mi_nginx
cat mi_nginx.te
Si las reglas del archivo .te son razonables, instala el módulo compilado:
sudo semodule -i mi_nginx.pp
sudo semodule -l | grep mi_nginx
Para eliminarlo más adelante:
sudo semodule -r mi_nginx
Solución de problemas
- Un servicio falla solo en enforcing: ejecuta
sudo ausearch -m AVC -ts recent -c nombre_del_proceso. Si hay denegaciones, sigue el orden: contexto de archivo, puerto, booleano y, en último caso, módulo propio. - Los contextos vuelven a estar mal tras un reetiquetado: usaste
chconen lugar desemanage fcontext. Registra la regla consemanagey ejecutarestorecon. - El sistema no arranca tras activar enforcing: en el menú de GRUB, edita la entrada y añade
enforcing=0a la línea del kernel para arrancar en permissive. Después ejecutasudo touch /.autorelabel, reinicia y revisa las denegaciones. - Un contenedor de Podman no puede leer un volumen: monta el volumen con la opción
:Z(privado) o:z(compartido) para que Podman lo reetiquete.
Conclusión
Ya sabes consultar y cambiar el modo de SELinux, corregir contextos de archivo con semanage fcontext y restorecon, permitir puertos no estándar, activar booleanos y diagnosticar denegaciones con ausearch y sealert. Con estas herramientas no hace falta desactivar SELinux para que un servicio funcione. Como siguientes pasos, puedes revisar semanage fcontext -l -C y semanage port -l -C para documentar tus cambios locales, configurar firewalld con zonas específicas y aplicar el mismo método a PHP-FPM o a tu base de datos.
