Seguridad

Guía Definitiva: Configura AppArmor para Proteger tus Aplicaciones en Linux

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 2-3 horas para el switch a enforcing.
  • Requisitos: Root, kernel moderno, 2-4 semanas en permissive antes de enforcing.
  • Resultado: Sistema con MAC activo, logs en /var/log/audit/audit.log.
  • Troubleshooting: audit2allow, audit2why, setenforce 0 para emergencias.

Por qué necesitas AppArmor en 2026

Un firewall con UFW protege los puertos. SSH con claves ed25519 protege el login. Fail2ban protege contra fuerza bruta. Pero si un atacante consigue ejecutar una shell como www-data dentro de tu nginx, o explota una vulnerabilidad en un binario con cap_net_raw, ninguna de esas capas lo detiene. Ahí entra AppArmor: confina el proceso mismo, no la red ni la autenticación.

AppArmor es un Linux Security Module (LSM) del kernel Linux que viene preinstalado y activo por defecto en Ubuntu y en openSUSE, y disponible como paquete en Debian y Arch. Aplica Mandatory Access Control (MAC) por encima del Discretionary Access Control (DAC) tradicional de Unix, así que aunque un usuario sea root, si el perfil del proceso no le concede acceso a una ruta, ese acceso se deniega. En esta guía aprenderás qué es, cómo leer un perfil existente, y cómo escribir el tuyo propio para confinar nginx, firefox o cualquier binario de tu servidor.

Qué es AppArmor y cómo encaja en el kernel

El Linux Security Module framework es una capa de hooks definidos en el código fuente del kernel. Cuando un proceso intenta abrir un archivo, hacer un socket o ejecutar otro binario, el kernel pasa por uno de esos hooks y le pregunta a cada módulo de seguridad registrado qué dice la política. AppArmor es uno de esos módulos: usa un modelo path-based (las reglas se definen por rutas como /etc/nginx/**) en contraste con SELinux, que usa etiquetas en inodos.

La consecuencia práctica es que los perfiles AppArmor son legibles a primera vista: una ruta, un modo de acceso y listo. SELinux requiere contexto, usuario, rol y tipo para cada objeto. La contrapartida es que AppArmor no entiende de etiquetas MLS ni de políticas multi-tenant tan granulares, pero para el 95% de los servidores Linux eso es más que suficiente.

Para verificar que está cargado en tu sistema:

sudo aa-status

Salida típica en Ubuntu 24.04 LTS:

apparmor module is loaded.
119 profiles are loaded.
24 profiles are in enforce mode.
   /usr/bin/man
   /usr/sbin/cups-browsed
   ...

Si ves apparmor module is loaded y luego la lista de perfiles, está activo. Si solo dice apparmor module is loaded. sin perfiles, Ubuntu cargó el módulo pero ningún paquete instaló perfiles todavía (raro en Ubuntu pero común en Debian minimal).

DAC vs MAC: el cambio de modelo mental

El modelo DAC lleva cuarenta años en Unix: si el dueño del archivo te da permiso, lo abres. Si root te da setuid, te vuelves root. Es flexible, pero también es el origen de la mayor parte de los incidentes de seguridad: un servicio con permisos excesivos que un atacante pivota hacia otro lado.

MAC invierte la pregunta. Por defecto, nadie puede hacer nada. Cada permiso debe estar explícitamente concedido en una política del sistema. Si tu nginx legítimo solo necesita leer /var/www/html/ y escribir en /var/log/nginx/, AppArmor le prohíbe acceder a /etc/shadow, /home/ o /root/ aunque el proceso corra como root. Cuando un 0-day permite ejecución remota en nginx, el daño se queda dentro de /var/www.

Característica DAC (Unix clásico) MAC (AppArmor)
Quien decide los permisos El dueño del recurso Una política del sistema
Permisos por defecto Definidos por bits rwx Denegado, hay que permitir explícitamente
Escalada de root setuid rompe el aislamiento El perfil mantiene límites aunque seas root
Modelo de configuración Inode + bits Path-based (AppArmor) o label-based (SELinux)
Curva de aprendizaje Mínima Media: requiere diseñar la política

AppArmor no reemplaza UFW, SSH seguro o fail2ban. Lo complementan: red, autenticación y proceso son tres capas distintas, y AppArmor se especializa en la tercera.

Anatomía de un perfil: leyendo /bin/ping

Los perfiles viven en /etc/apparmor.d/ con el nombre del binario, sustituyendo / por .. El perfil de /bin/ping está en /etc/apparmor.d/bin.ping:

#include <tunables/global>
/bin/ping flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/consoles>
  #include <abstractions/nameservice>
  capability net_raw,
  capability setuid,
  network inet raw,
  /bin/ping mixr,
  /etc/modules.conf r,
}

Cada línea significa algo concreto:

  • #include <tunables/global>: importa variables reutilizables (paths comunes, versiones).
  • /bin/ping flags=(complain): ruta absoluta al binario + modo inicial. flags=(complain) hace que las violaciones se registren pero no bloqueen, ideal mientras desarrollas el perfil. Cámbialo a flags=(enforce) cuando esté afinado.
  • #include <abstractions/base>: importa permisos comunes (bibliotecas compartidas de glibc, acceso al journal, pseudo-devices como /dev/null). Reusar abstracciones evita perfiles duplicados.
  • capability net_raw,: permite la capability de Linux CAP_NET_RAW, necesaria para enviar paquetes ICMP sin ser root completo.
  • network inet raw,: permite tráfico IPv4 raw sockets (el ping usa sockets raw).
  • /bin/ping mixr,: concede permiso memory map, iinherit, xecute y read sobre /bin/ping.
  • /etc/modules.conf r,: solo lectura sobre ese archivo (necesario porque ping consulta módulos cargados).

El patrón clave: cero reglas significa cero permisos. Si una capability no está listada, está denegada aunque seas root. Si una ruta no aparece, está denegada aunque el proceso intente acceder.

Tu primer perfil: confinar nginx paso a paso

Asumamos nginx instalado en Ubuntu 24.04 y un sitio sirviendo desde /var/www/html con logs en /var/log/nginx/. El paquete nginx-common en Ubuntu ya trae un perfil AppArmor en /etc/apparmor.d/usr.sbin.nginx, pero aquí vamos a escribir uno a medida para entender el proceso.

1. Empieza en modo complain

Antes de activar el enforce necesitamos ver qué hace nginx realmente. Ponemos el perfil que vamos a escribir en complain:

sudo nano /etc/apparmor.d/local/usr.sbin.nginx

Escribe lo siguiente:

#include <tunables/global>
/usr/sbin/nginx flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/web>
  #include <abstractions/nameservice>
  capability net_bind_service,
  capability dac_override,
  capability setuid,
  capability setgid,
  /usr/sbin/nginx mr,
  /etc/nginx/** r,
  /var/www/** r,
  /var/log/nginx/** w,
  /var/lib/nginx/** rw,
  /run/nginx.pid rw,
}

Recarga el perfil en el kernel:

sudo apparmor_parser -r /etc/apparmor.d/local/usr.sbin.nginx
sudo aa-complain /usr/sbin/nginx

2. Genera tráfico real con aa-logprof

Reinicia nginx y ejecuta un set de operaciones que ejercite cada camino: servir páginas estáticas, hacer un proxy_pass a un backend, procesar uploads si los tienes, etc. Todo lo que nginx intente hacer fuera del perfil quedará registrado, pero no bloqueado (porque está en complain).

sudo systemctl restart nginx
curl -s http://localhost/ # al menos una petición para forzar la carga del binario
sudo aa-logprof

aa-logprof lee /var/log/audit/audit.log y /var/log/syslog, te pregunta por cada evento nuevo qué permiso debería tener, y actualiza el perfil automáticamente. Responde A (allow) para las rutas legítimas y revisa cada propuesta antes de aceptar.

3. Verifica que el perfil cierra las brechas obvias

Antes de pasar a enforce, fuerza accesos que sabemos que NO deberían permitirse. Por ejemplo, intenta que nginx lea /etc/shadow con un proxy_pass malicioso simulado:

sudo -u www-data cat /etc/shadow

En modo complain esto registra la denegación en /var/log/syslog o audit.log con un apparmor="DENIED". Verifica que el log lo captura antes de promover a enforce.

4. Promueve a enforce

Cuando estés conforme con la cobertura, activa el modo estricto:

sudo aa-enforce /usr/sbin/nginx
sudo systemctl restart nginx
sudo aa-status | grep nginx

Si nginx sigue funcionando y aa-status muestra la ruta con enforce, tienes el perfil vivo. Si algo se rompe, vuelve a complain con sudo aa-complain /usr/sbin/nginx y revisa los logs otra vez.

Modos enforcing y complain: cuándo usar cada uno

AppArmor soporta dos modos por perfil más un tercero global:

Modo Efecto en violación Cuándo usarlo
enforce Bloquea la syscall y registra el evento Producción: cuando el perfil está verificado contra una suite de pruebas.
complain Permite la syscall y registra el evento Desarrollo de perfiles. También el modo por defecto de paquetes como apparmor-profiles-extra.
unconfined El proceso corre sin perfil Estado inicial o fallback mientras auditas un binario legado.

El comando para alternar entre enforce y complain no requiere recargar nginx:

sudo aa-enforce /usr/sbin/nginx
sudo aa-complain /usr/sbin/nginx

Y la inspección rápida de todos los perfiles en sus modos:

sudo aa-status --pretty

Para firefox de escritorio es común dejarlo en complain porque un navegador legítimo hace cosas impredecibles (descargas en ~/Downloads, sockets a puertos arbitrarios, JIT de JS). Para servidores stateless como nginx, cron o un binario de CI, el enforce es viable desde el día uno siempre que tengas manera de generar tráfico que ejercite todas las rutas (curl, scripts de prueba, k6).

Debugging cuando algo se rompe

Si un proceso que ayer funcionaba hoy falla con Permission denied aunque el path existe, sospecha de AppArmor antes de revisar permisos de archivo. La traza vive en /var/log/syslog (cuando AppArmor usa el backend de syslog) o en /var/log/audit/audit.log (cuando hay auditd instalado):

sudo journalctl -k --since "10 minutes ago" | grep -i apparmor
sudo grep -i "apparmor.*DENIED" /var/log/audit/audit.log | tail -20

Línea típica de denegación:

[1521056.552037] audit: type=1400 audit(1571868402.378:24425): apparmor="DENIED" operation="open" profile="/usr/sbin/cups-browsed" name="/var/lib/libvirt/dnsmasq/" pid=1128 comm="cups-browsed" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

Decodifícala así: operation="open" + profile="/usr/sbin/cups-browsed" + name="/var/lib/libvirt/dnsmasq/" + denied_mask="r". cups-browsed quería leer /var/lib/libvirt/dnsmasq/ como root y AppArmor lo bloqueó. La solución es agregar /var/lib/libvirt/dnsmasq/** r, al perfil, recargar con apparmor_parser -r y verificar.

Si quieres que AppArmor NO loguee algo específico (ruido operativo, log spam), usa audit deny /ruta/** en el perfil para silenciar ese path.

Cuándo AppArmor NO es suficiente

AppArmor es la opción correcta para la mayoría de servidores Linux modernos, pero tiene límites que conviene conocer antes de comprometerse con su modelo:

  • Políticas multi-tenant estrictas o MLS: SELinux tiene type enforcement y multilevel security más granulares. Si trabajas con clasificación BLP o compartimentos militares, SELinux es la elección.
  • Aislamiento de procesos a nivel kernel con namespaces: AppArmor no reemplaza a firejail, bubblewrap ni a los namespaces de Docker/podman. Para contener qué ve un proceso (sistema de archivos, red, PID) usa namespaces. Para contener qué puede hacer un proceso con lo que ve, usa AppArmor.
  • Distros que ya están comprometidas con SELinux: Fedora, RHEL y CentOS Stream traen SELinux y abandonar AppArmor requiere algo de trabajo. Si admins esa familia, SELinux es la apuesta correcta.
  • Binarios que cambian de path frecuentemente: como el modelo es path-based, si tu app vive en /opt/v1.42/myapp y se actualiza a /opt/v1.43/myapp, el perfil deja de aplicar. Usa enlaces simbólicos estables o globs conservadoras como /opt/myapp/**.

Si necesitas combinarlo con SELinux, Ubuntu lo permite deshabilitando AppArmor vía /etc/default/grub + apparmor=0 + update-grub. Eso sí, una vez deshabilitado el kernel no carga el módulo y todos los perfiles quedan inertes hasta que vuelvas a activarlo con un reboot.

Resumen y checklist

AppArmor te da confinamiento por proceso basado en perfiles de texto legibles. Una vez activado, incluso si un atacante consigue ejecutar código en tu nginx, firefox o cron, los paths y capabilities que el perfil no haya concedido quedan vetados a nivel kernel. La inversión de tiempo es moderada: una tarde para tu servicio más crítico, y la mayoría de paquetes comunes en Ubuntu ya traen perfiles preinstalados.

  • Verifica aa-status en cada servidor y mira cuántos perfiles ya están en enforce sin que lo sepas.
  • Para servicios personalizados, escribe un perfil mínimo en complain, genera tráfico real con aa-logprof, y solo entonces pasa a enforce.
  • Apunta journalctl -k | grep -i apparmor como parte del runbook de tu SRE para diagnosticar fallos “misteriosos”.
  • Integra AppArmor en tu imagen base (cloud-init, packer, Dockerfile) para que cada VM/instance arranque con un set de perfiles canónico.

Preguntas frecuentes

¿Por qué Guía Definitiva: Configura AppArmor para Proteger tus Aplicaciones en Linux es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con Guía Definitiva: Configura AppArmor para?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con Guía Definitiva: Configura AppArmor para?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar Guía Definitiva: Configura AppArmor para y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a Guía Definitiva: Configura AppArmor para?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *