Domina SELinux en Modo Enforcing: Guía Paso a Paso para Sysadmins Linux
TL;DR — Lo que vas a tener al final
Este artículo te lleva de cero a producción con SELinux en modo enforcing: arrancamos explicando qué es y por qué sigue siendo la opción más sólida de control de acceso obligatorio (MAC) en Linux en 2026, pasamos por los tres modos de operación, definimos una metodología segura para migrar de permissive a enforcing sin romper servicios en producción, y cerramos con los comandos del día a día, troubleshooting de denegaciones AVC y un checklist que puedes aplicar hoy en Fedora 41, Rocky Linux 9, Debian 12 o Ubuntu 24.04.
TL;DR — Lo que vas a tener al final
- Tiempo de setup completo: 2 a 4 horas la primera vez, contando el ciclo de entrenamiento en
permissive. - Requisitos: Distro con SELinux (RHEL/Fedora/Rocky/Alma/Debian/Ubuntu), acceso root, ssh-agent para evitar quedarte fuera.
- Resultado: servidor con denegaciones activas por defecto, política auditada y módulos personalizados listos.
Qué es SELinux y por qué sigue importando en 2026
SELinux (Security-Enhanced Linux) es un framework de Mandatory Access Control (MAC) desarrollado originalmente por la NSA e integrado en el kernel Linux desde 2003. A diferencia del control de acceso discrecional (DAC) clásico de Unix — donde el usuario decide qué hace cada archivo — SELinux impone políticas que el usuario root no puede saltarse. Si un proceso es comprometido, SELinux limita qué recursos puede tocar aunque el atacante haya obtenido privilegios.
En 2026 la superficie de ataque creció bastante: contenedores, microservicios, pipelines CI/CD con agentes privilegiados y workloads de IA que escriben a disco constantemente. Un firewall te protege de la red, pero no de un usuario que subió un script PHP malicioso a tu aplicación y consigue ejecución como apache. Ahí es donde SELinux aporta valor real: aunque el atacante ya esté dentro del proceso, no puede escapar del contexto que la política le asignó.
Los tres modos de SELinux
SELinux opera en tres modos excluyentes. La diferencia entre permissive y enforcing no es solo “qué pasa cuando algo se bloquea”: cambia toda la semántica de tu servidor.
| Modo | Comportamiento | Cuándo se usa | Riesgo |
|---|---|---|---|
disabled |
Kernel no aplica políticas; el módulo está apagado por completo. | Diagnóstico extremo o migración desde AppArmor. | Sin protección MAC. No recomendado en producción. |
permissive |
Carga la política y registra denegaciones en el log, pero NO bloquea. | Auditoría, entrenamiento, troubleshooting. | Visibilidad sin enforcement. Sirve para detectar huecos. |
enforcing |
Aplica la política y bloquea activamente lo no permitido. | Producción. | Si faltan reglas, servicios legítimos se rompen. Por eso se llega aquí tras un ciclo en permissive. |
Verifica el modo actual con getenforce y el detalle completo con sestatus:
getenforce
# Salida típica: Enforcing
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
El cambio en vivo entre enforcing y permissive se hace con setenforce, pero el cambio persistente va en /etc/selinux/config. Más sobre esto en la siguiente sección.
Cómo migrar de permissive a enforcing sin romper producción
El error más común que veo en sysadmins es saltar directo de disabled o permissive sin auditoría previa y terminar con servicios como Nginx, PostgreSQL o Docker que dejan de arrancar. La metodología correcta es un ciclo de tres pasadas:
Pasada 1: validar que la política carga limpia
Antes de tocar nada, confirma que SELinux está habilitado y el sistema arranca sin errores en permissive:
sudo setenforce Permissive
sudo sestatus | grep "Current mode"
# Current mode: permissive
sudo systemd-analyze verify
# Si systemd reporta unidades fallando por SELinux, hay un problema de base.
Pasada 2: entrenar el sistema
Deja el sistema corriendo en permissive al menos una semana en producción real. Haz las tareas que un usuario haría: reinicia servicios, despliega código, ejecuta cron jobs. Cada denegación quedará registrada en /var/log/audit/audit.log.
Genera un reporte de lo que SELinux habría bloqueado:
sudo ausearch -m AVC,USER_AVC -ts recent --interpret | tee /tmp/selinux_audit_recent.txt
# Opcional: módulo personalizado a partir de las denegaciones observadas
sudo audit2allow -M mi_politica_custom < /tmp/selinux_audit_recent.txt
Pasada 3: activar enforcing de forma segura
Antes de cambiar el modo, abre una sesión SSH adicional con ssh-agent y deja una consola de respaldo. Si el cambio rompe tu sesión, la consola auxiliar te salva. Luego:
sudo setenforce Enforcing
sudo sestatus | grep "Current mode"
# Current mode: enforcing
# Si todo funciona tras 24h, persiste el cambio
sudo sed -i 's/^SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config
sudo grep ^SELINUX= /etc/selinux/config
# SELINUX=enforcing
Troubleshooting típico: si un servicio como Nginx falla al arrancar en enforcing, revisa journalctl -xe y ausearch -m AVC -ts recent. El 80% de las veces es un contexto mal etiquetado en un directorio nuevo o un puerto no declarado. Las siguientes secciones cubren ambos casos.
Conceptos clave que vas a usar todo el tiempo
SELinux tiene cuatro primitivas que necesitas manejar con soltura. No te obsesiones con la teoría: aprende a leerlas en logs y comandos, y el resto encaja solo.
| Concepto | Qué representa | Ejemplo real |
|---|---|---|
| Usuario | Rol de alto nivel independiente del usuario Unix. | system_u, unconfined_u, guest_u. |
| Rol | Puente entre usuario y tipo; define qué tipos puede asumir. | object_r para recursos, system_r para procesos del sistema. |
| Tipo | La pieza más usada. Define qué puede hacer un proceso o recurso. | httpd_t para procesos de Nginx/Apache, httpd_sys_content_t para archivos servibles. |
| Contexto | La cadena completa usuario:rol:tipo[:nivel] que viaja en cada objeto. |
system_u:object_r:httpd_sys_content_t:s0 en archivos de un sitio web. |
Para ver el contexto de un archivo o proceso:
ls -Z /var/www/html/
# -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
ps -eZ | grep nginx
# system_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx
El flag -Z aparece en ls, ps, id, cp, mv y netstat. Memorízalo: cada vez que algo no funciona en enforcing, lo primero es mirar el contexto.
Comandos del día a día
Esta es la caja de herramientas mínima. Después de una semana usándolos, los ejecutas sin pensar.
Estado y modo
getenforce # modo actual: enforcing/permissive/disabled
sestatus # estado completo
setenforce 0 # pasar a permissive (temporal)
setenforce 1 # pasar a enforcing (temporal)
Booleanos: ajustes finos sin compilar política
Los booleanos son interruptores predefinidos que ajustan la política sin necesidad de recompilar. Son la primera línea de solución cuando un servicio no arranca.
getsebool -a # listar todos
getsebool httpd_can_network_connect_db
# httpd_can_network_connect_db --> off
setsebool httpd_can_network_connect_db on # cambio en runtime
setsebool -P httpd_can_network_connect_db on # cambio persistente (sobrevive reboot)
El flag -P (persistent) escribe el cambio al binario de la política. Sin él, el cambio se pierde al reiniciar. Úsalo cuando confirmes que el ajuste es correcto.
Contextos de archivo
semanage fcontext -l | grep httpd # ver contextos por defecto
restorecon -Rv /var/www/html/ # restaurar contexto por defecto
chcon -t httpd_sys_content_t /var/www/html/index.html # cambio manual (mejor evitar)
semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?" # declarar regla persistente
Regla de oro: usa restorecon siempre que puedas. Los cambios manuales con chcon se pierden si el sistema reetiqueta con touch /.autorelabel && reboot. Si necesitas un contexto nuevo, decláralo con semanage fcontext.
Puertos permitidos
semanage port -l | grep http
# http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
# Añadir puerto no estándar (ejemplo: app en 8081)
sudo semanage port -a -t http_port_t -p tcp 8081
Troubleshooting: leer y arreglar denegaciones AVC
Cuando un proceso se queja en enforcing, el primer paso es leer el log. La denegación tiene un formato consistente que se aprende rápido.
Encontrar la denegación
sudo ausearch -m AVC -ts today
# type=AVC msg=audit(1729123456.789:1011): avc: denied { read } for pid=4321 comm="nginx"
# name="secret.key" dev="sda3" ino=12345 scontext=system_u:system_r:httpd_t:s0
# tcontext=system_u:object_r:admin_home_t:s0 tclass=file permissive=0
Cada línea te dice: quién (scontext) intentó qué acción sobre qué recurso (tcontext) y con qué clase (tclass). Aquí, Nginx (httpd_t) quiso leer (read) un archivo etiquetado como admin_home_t, lo cual no está permitido por la política targeted.
Explicación automática
sudo ausearch -m AVC -ts today | audit2why
# Por qué: el tipo httpd_t no tiene permiso read sobre admin_home_t.
# Solución sugerida: permitir el acceso vía booleano o relabel.
Crear módulo permisivo SOLO para un servicio problemático
Si auditar la denegación y ajustar booleanos no basta — por ejemplo, una aplicación legacy que no puedes reetiquetar — puedes hacer que SELinux trate un único dominio como permissive sin afectar al resto del sistema:
sudo semanage permissive -a httpd_t
# httpd_t ahora se audita pero no se bloquea. El resto sigue en enforcing estricto.
sudo semanage permissive -l
# Customized Permissive Types:
# httpd_t
# Para revertir:
sudo semanage permissive -d httpd_t
Esta es la salida de emergencia cuando una app crítica no coopera con la política. Documenta siempre el motivo en un comentario en tu sistema de gestión de configuración, porque un dominio permisivo indefinido es una deuda técnica que tarde o temprano explota.
Módulos personalizados: cuándo y cómo
La política targeted que viene por defecto cubre el 95% de los casos. Cuando tienes un servicio custom que no existe en los repos oficiales — un binario interno, una app Go compilada en casa — necesitarás un módulo que le permita acceder a sus archivos legítimos.
Generar módulo a partir de denegaciones
# 1. Identifica las denegaciones del servicio
sudo ausearch -m AVC -ts recent -c mi_app
# 2. Genera el módulo
sudo ausearch -m AVC -ts recent -c mi_app | audit2allow -M mi_app
# 3. Revisa el .te antes de cargar (paso crítico: auditoría humana)
cat mi_app.te
# 4. Carga el módulo
sudo semodule -i mi_app.pp
# 5. Verifica que el módulo quedó registrado
sudo semodule -l | grep mi_app
Cuándo NO crear un módulo permisivo global
Si tu generación de audit2allow produce decenas de reglas permisivas de tipo allow * * * *, tienes un problema de diseño: el servicio corre como unconfined_t y no hay política que aplicarle. Mejor reetiquetar el binario con su propio dominio que abrir la política entera.
# Identifica si el binario corre unconfined
ps -eZ | grep mi_app
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 ... mi_app
# Solución: asignar dominio correcto y regenerar política
sudo semanage fcontext -a -t bin_t /usr/local/bin/mi_app
sudo restorecon -v /usr/local/bin/mi_app
Buenas prácticas y checklist final
Antes de cerrar, aplica este checklist al servidor que estés endureciendo. Si falla cualquier punto, vuelve a la sección correspondiente y ajusta.
- Modo persistente correcto:
/etc/selinux/configdiceSELINUX=enforcing, nopermissivenidisabled. - Política cargada:
sestatusreportaLoaded policy name: targetedy versión actual del kernel. - Sin denegaciones críticas:
ausearch -m AVC -ts today | audit2whyno devuelve nada o solo ajustes cosméticos (booleanos). - Módulos personalizados documentados: cada
*.ppbajo/etc/selinux/targeted/modules/active/modules/tiene un*.teen tu repo interno con motivo y autor. - Puertos personalizados declarados: nada corre en puertos no estándar sin estar en
semanage port -l. - Contextos validados en archivos servidos:
ls -Z /var/www/muestra contextos de tipohttpd_sys_content_t, noadmin_home_tnitmp_t. - Booleanos persistentes auditados:
semanage boolean -l -nmuestra los cambios que persististe con-Py tienen justificación. - Reetiquetado tras instalar paquetes grandes: tras instalar Postgres o un kernel nuevo, ejecuta
restorecon -Rv /var/lib/pgsql/o reetiqueta el sistema si actualizaste muchos paquetes.
Con esto tu servidor queda en enforcing con política auditada, denegaciones activas y módulos custom listos. El siguiente paso lógico es integrar SELinux en tu sistema de gestión de configuración (Ansible, Puppet, Chef) para que el estado se mantenga tras cada despliegue.
Preguntas frecuentes
¿Cuánto tarda en configurar SELinux en un servidor en producción?
Entre 2 y 4 horas para un servidor nuevo con servicios estándar (Nginx, PostgreSQL, SSH). Para entornos con aplicaciones custom, suma al menos una semana en permissive auditando antes de pasar a enforcing.
¿Qué diferencia hay entre SELinux y AppArmor?
SELinux es MAC basado en etiquetas (type enforcement), con política centralizada y granularidad alta. AppArmor es MAC basado en paths, más simple de configurar pero menos flexible. Son excluyentes: solo uno puede estar activo por sistema. Aprende a elegir en nuestro artículo AppArmor vs SELinux en 2026.
¿SELinux afecta el rendimiento del servidor?
El overhead medido es inferior al 2% en cargas típicas. En benchmarks de base de datos intensiva el impacto llega al 5%, pero el beneficio de seguridad lo compensa con creces. El overhead sube si ejecutas en permissive con mucha denegación registrada porque cada AVC requiere I/O a audit log.
¿Qué hago si mi servidor remoto pierde SSH al pasar a enforcing?
Conéctate por consola IPMI, KVM o la consola serial del proveedor. Desde ahí puedes ejecutar setenforce 0 para volver a permissive y revisar los logs sin perder acceso. Por eso la recomendación de tener siempre una sesión SSH auxiliar antes del cambio.
¿Es compatible SELinux con Docker y contenedores?
Sí, y es la mejor práctica. Docker usa el driver selinux por defecto, que aplica svirt_sandbox_t a cada contenedor. No ejecutes Docker en modo --privileged sin una razón clara, porque eso anula el aislamiento SELinux.
¿Cómo desactivo SELinux si realmente no puedo convivir con él?
Edita /etc/selinux/config, cambia SELINUX=enforcing a SELINUX=disabled, y tras el reboot configura AppArmor como reemplazo. No mantengas un sistema con SELinux cargado pero en permissive permanente; eso no aporta seguridad y sí añade complejidad.
