Seguridad

Configuración de AppArmor para endurecer aplicaciones en Linux

AppArmor es el sistema de MAC (Mandatory Access Control) que viene por default en Debian y Ubuntu, y la mayoría de los sysadmins lo dejan en modo “complain” o directamente lo desactivan. Esa es la peor decisión posible — está ahí, funciona, y agrega una capa de defensa real cuando un servicio es comprometido. Esta guía documenta la transición de complain a enforce en producción sin romper nada: el approach gradual de profile por profile, los perfiles canónicos para nginx/PostgreSQL/Python/Node, y cómo debuggear cuando AppArmor bloquea algo legítimo.

Si quieres una capa de seguridad adicional sin la complejidad de SELinux, AppArmor es tu amigo.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 2-3 horas para enforce en 5 servicios comunes, 1 hora por servicio adicional.
  • Requisitos: Debian/Ubuntu con AppArmor instalado (viene por default), root.
  • Resultado: Servicios críticos en modo enforce, profiles documentados, alertas a syslog.
  • Troubleshooting típico: perfil que bloquea legítimo, modo enforce rompe upgrade. Solución: `aa-complain` + `aa-logprof`.

Por qué AppArmor sigue siendo relevante en 2026

Cada año aparecen CVEs nuevos en demonios que corren como root: nginx, php-fpm, dovecot, postfix, redis. El patrón siempre es el mismo: el atacante entra por una vulnerabilidad de la aplicación y termina con permisos totales sobre la máquina porque el proceso corre sin restricciones. AppArmor existe exactamente para romper ese último eslabón: confina cada aplicación a un perfil declarativo que dice qué puede leer, qué puede escribir, qué capabilities de root puede usar y qué puede invocar a nivel de red. Si el atacante explota el binario, sigue atrapado dentro del perfil.

AppArmor es el Mandatory Access Control (MAC) por defecto en Debian, Ubuntu, openSUSE, SUSE Linux Enterprise y derivados. En RHEL, Fedora y derivados el MAC por defecto es SELinux. Esa decisión histórica importa porque determina qué herramientas vas a usar, dónde están los logs y qué sintaxis de perfil toca aprender. Si vienes de CentOS o AlmaLinux y migras a Ubuntu, este cambio es probablemente el más visible de toda la transición. Lo vimos con detalle en AppArmor vs SELinux en 2026: cómo elegir y empezar; aquí vamos directo a la práctica: cómo instalar, habilitar, escribir perfiles y auditar el comportamiento de tus aplicaciones en producción.

Qué resuelve AppArmor y qué no

AppArmor trabaja por path, no por inode ni por label de objeto. Esto lo hace más fácil de leer que SELinux: una regla tipo /var/www/** r, se entiende sin tener que memorizar contextos MLS. La contrapartida es que un atacante que controle un path permitido hereda lo que el perfil permite sobre ese path. AppArmor no te salva si tu binario está en /tmp con permisos de escritura para todos y tu perfil dice /tmp/** rwk,.

Lo que AppArmor sí hace bien:

  • Limitar el acceso a archivos más allá del usuario Unix clásico (DAC).
  • Restringir capabilities de root — un nginx confinado puede abrir puertos por debajo de 1024 sin ser root, pero no puede montar sistemas de archivos ni cargar módulos del kernel.
  • Filtrar operaciones de red por protocolo (TCP, UDP, raw) en perfiles nuevos.
  • Aislar procesos hijos con hats para CGI, FastCGI, workers.
  • Forzar no-execute y audit sobre paths individuales.

Lo que no hace (y conviene no esperar):

  • No reemplaza un firewall. Si una regla permite network inet stream, el proceso puede hablar a cualquier IP; si quieres filtrar por destino necesitas nftables o un sidecar.
  • No es un antiexploit. Un RCE dentro del perfil sigue siendo un RCE — solo que el blast radius es menor.
  • No protege contra rootkits que carguen módulos del kernel. Eso requiere module_lock o SecureBoot verificado.

Instalación y módulos del kernel

En Ubuntu y Debian AppArmor viene preinstalado y los perfiles se cargan desde /etc/apparmor.d/. El módulo LSM se compila dentro del kernel en estas distros (CONFIG_SECURITY_APPARMOR=y), así que no hace falta cargar nada. Verifica el estado antes de empezar a tocar nada:

# Verifica que el módulo está activo en el kernel y que los perfiles cargados
sudo aa-status
# En sistemas sin aa-status (inmersiones mínimas, contenedores)
sudo cat /sys/kernel/security/apparmor/profiles | head -20
# Paquetes opcionales para auditoría y herramientas de desarrollo
sudo apt install apparmor-utils apparmor-notify apparmor-profiles apparmor-profiles-extra

aa-status imprime una tabla con tres secciones clave: la cantidad de perfiles cargados, cuántos están en modo enforce y cuántos en complain. En una instalación fresca de Ubuntu Server 24.04 verás entre 60 y 90 perfiles, casi todos en enforce (los trae el paquete apparmor-profiles). apparmor-utils trae las herramientas de generación de perfiles que vamos a usar más adelante. Si trabajas en Fedora, RHEL o derivados, AppArmor no está compilado en el kernel por defecto — toca cargar el módulo LSM o cambiar la línea de arranque del kernel con apparmor=1 security=apparmor.

Modos de perfil: enforce, complain y unconfined

Cada perfil tiene tres estados posibles en el sistema de archivos:

  • enforce: el kernel bloquea las operaciones no permitidas y las registra en /var/log/syslog o journalctl -t audit.
  • complain: el kernel deja pasar la operación pero la registra igual. Sirve para entrenar un perfil sin riesgo a romper el servicio.
  • unconfined: el proceso corre sin perfil. Es el equivalente a no tener AppArmor.

El switch entre enforce y complain es transparente para la aplicación — no hay que reiniciar el binario. Esto hace que la transición de “perfil nuevo” a “perfil validado en producción” sea muy barata:

# Poner un perfil en complain (modo aprendizaje, no rompe nada)
sudo aa-complain /usr/sbin/nginx
# Devolverlo a enforce cuando ya confías en las reglas
sudo aa-enforce /usr/sbin/nginx
# Desactivar un perfil temporalmente sin borrarlo
sudo ln -s /etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx

El truco del symlink en disable/ es el patrón oficial que usan los paquetes Debian/Ubuntu: apparmor_parser -R recarga la lista de perfiles y elimina el indicado del kernel sin tocar el archivo en disco. Para revertirlo basta con borrar el symlink y volver a invocar apparmor_parser -a. Si tocas un perfil a mano y quieres probarlo sin reiniciar el demonio, también puedes recargarlo en caliente con apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx (la r es de replace).

Perfiles stock: lo que ya tienes sin saberlo

Los paquetes apparmor-profiles y apparmor-profiles-extra incluyen decenas de perfiles listos para usar: usr.sbin.apache2, usr.sbin.dovecot, usr.sbin.mysqld, usr.sbin.ntpd, usr.sbin.dnsmasq, usr.sbin.rsyslogd, entre otros. La regla no escrita de Debian es: si instalas un daemon desde los repos oficiales, hay un perfil asociado listo para enforce. Antes de tocar nada verifica:

# Lista todos los perfiles cargados en el kernel con su modo
sudo aa-status --pretty-json | jq '.profiles[] | {name, mode}'
# Equivalente legible en texto plano
sudo aa-status | grep -E '^s+S' | awk '{print $1, $4}' | column -t

Si el paquete de tu daemon está instalado y el perfil aparece como complain, casi siempre significa que el mantenedor del paquete no está 100% seguro de que el perfil cubra todos los flujos de trabajo. A veces una feature opcional (por ejemplo mod_userdir en Apache) requiere reglas adicionales que el perfil stock no incluye. Lo correcto: deja el perfil en complain una semana con tráfico real, mira los logs, y recién entonces mové a enforce.

Crear tu primer perfil con aa-genprof

aa-genprof es la herramienta estrella para crear perfiles desde cero. Funciona en modo monitor: corre el binario en complain, observa qué archivos y capabilities toca durante una ventana de uso, y al salir te pregunta qué permitir y qué denegar. El flujo completo:

# 1. Crear un perfil vacío en complain para tu aplicación
sudo aa-genprof /usr/local/bin/mi-app
# aa-genprof abre una sesión interactiva. Mientras corre:
#   - ejecuta tu aplicación en otro trabajo y usa todas las features
#   - leé/escribí archivos en distintos paths
#   - abrí sockets, haz conexiones de red
#   - terminá la sesión con S (Scan) cuando hayas cubierto todo

Al finalizar aa-genprof revisa los eventos en /var/log/syslog y los agrupa. Para cada archivo te pregunta:

  • Allow: permite el acceso (escribe la regla en el perfil).
  • Deny: bloquea y registra (escribe una regla deny explícita).
  • Abandon: descarta el evento (no genera regla).
  • Glob: si el path tiene una variable evidente, expande a un patrón (/var/log/mi-app/*.log w,).

El resultado es un archivo en /etc/apparmor.d/ con nombre usr.local.bin.mi-app listo para cargar. Hay tres consejos prácticos que ahorran horas de dolor:

  • haz la sesión de entrenamiento con la app en su modo normal de operación: con un usuario real, no como root en /root. Si la app normalmente corre como www-data, lanzala como www-data durante el aprendizaje.
  • Incluí los archivos de configuración en la sesión. Si abres /etc/mi-app/config.yaml en el entrenamiento pero no lo haces durante la captura, el perfil va a denegar lecturas legítimas después.
  • Si el binario llama a otros binarios (mi-app -> /usr/bin/curl), aa-genprof detecta los childs y te ofrece crearles perfil también. Decí Yes: un ix (inherit and execute) sobre el binario hijo deja que AppArmor le aplique su propio perfil, no el del padre.

Troubleshooting típico: si después de cargar el perfil el binario arranca pero se cae a los 30 segundos con Permission denied en un path específico, ponlo en complain, repetí el flujo problemático, y mira los eventos nuevos con sudo journalctl -t audit --since "5 minutes ago" | grep mi-app. La respuesta casi siempre es un path que olvidaste abrir durante el entrenamiento.

Editar perfiles a mano: reglas, capabilities y abstractions

El formato de un perfil es texto plano y se aprende rápido. Las reglas más comunes:

# Estructura básica de un perfil
#include <tunables/global>
profile mi-app /usr/local/bin/mi-app {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  capability dac_override,
  capability setuid,
  capability setgid,
  capability net_bind_service,
  /etc/mi-app/**           r,
  /var/lib/mi-app/         rwk,
  /var/log/mi-app/*.log    w,
  /usr/local/bin/mi-app    mr,
  /usr/bin/curl            ix,
  deny /etc/shadow          r,
  deny /home/*/.ssh/**      rwx,
  network inet stream,
  network inet6 stream,
}

Las letras al final de cada regla de archivo son las permisos:

  • r read, w write, k lock (flock), l link, m mmap_exec.
  • a append (escritura solo al final — útil para logs).
  • x execute (ejecutar y transición a otro perfil si existe).
  • ix inherit execute — el hijo corre con el perfil del padre.
  • px discrete profile execution — el hijo corre con su propio perfil (debe existir).
  • ux unconfined execute — el hijo corre sin AppArmor (evitalo).

Los #include cargan abstractions: fragmentos de reglas reutilizables que viven en /etc/apparmor.d/abstractions/. abstractions/base trae permisos para cosas que casi todo proceso necesita (leer /etc/nsswitch.conf, acceder al sistema de logs, etc.). usa abstractions siempre que puedas — un perfil con 200 reglas inline es mucho más difícil de mantener que uno con 8 includes y 20 reglas específicas.

Para las capabilities, la lista completa vive en /usr/include/linux/capability.h pero las más comunes en un servidor Linux son: net_bind_service (abrir puertos por debajo de 1024), sys_admin (cuidado, equivale a casi root), dac_override (saltar permisos Unix), sys_resource (cambiar rlimits), setuid/setgid (cambiar identidad), audit_write (escribir al subsistema de auditoría).

Hats para aplicaciones multi-proceso

Un hat es un sub-perfil que se activa con aa_change_hat() desde el código de la aplicación. Sirve para binarios que cambian de contexto según el request: Apache con CGI/FastCGI, nginx con varios workers sirviendo sitios distintos, servidores SMTP que manejan auth en una conexión y entrega en otra. El patrón:

# Perfil padre con hat por virtualhost
/usr/sbin/apache2 {
  #include <abstractions/apache2-common>
  capability dac_override,
  capability setuid,
  capability setgid,
  /var/www/**                      r,
  /etc/apache2/**                  r,
  /var/log/apache2/**              w,
  /usr/sbin/apache2                mr,
  # Hat: solo lo permitido cuando se sirve el sitio "cliente-a"
  ^cliente-a {
    /var/www/cliente-a/**          rwk,
    /var/log/apache2/cliente-a.log w,
    /etc/apache2/sites/cliente-a.conf r,
  }
  # Hat por defecto: cualquier otro sitio
  ^DEFAULT {
    /var/www/*                     r,
    /var/log/apache2/*.log         w,
  }
}

El hat se activa en Apache vía mod_apparmor con directivas AAHatName por virtualhost. En nginx no hay integración oficial; la forma práctica es usar change_hat() en un módulo Lua (OpenResty) o crear perfiles separados por worker y bindearlos con --procview. Para la mayoría de setups un solo perfil sin hats basta — los hats suman complejidad y solo los necesitas si hay rutas de código con riesgos distintos.

Logs y auditoría: cómo leer lo que el kernel está bloqueando

Las denegaciones se ven en el journal de auditoría. El truco está en filtrar bien:

# Solo eventos de AppArmor en tiempo real
sudo journalctl -t audit -f -k | grep -E 'apparmor=(DENIED|ALLOWED)'
# Resumen por perfil y operación (últimas 24h)
sudo journalctl -t audit --since "24 hours ago" | 
  awk '/apparmor=DENIED/ {match($0, /profile="[^"]+"/, p); match($0, /operation="[^"]+"/, o); print p[0], o[1]}' | 
  sort | uniq -c | sort -rn | head
# Notificación de escritorio cuando se bloquea algo (necesita apparmor-notify)
sudo aa-notify -s 1 -w 10

aa-logprof es el complemento natural de aa-genprof: lee los logs y te sugiere reglas para cubrir las nuevas denegaciones. El flujo recomendado en producción es:

  1. Deploy inicial con perfil en complain.
  2. Correr una semana con tráfico real, logs rotando.
  3. Ejecutar sudo aa-logprof, revisar las sugerencias, aprobarlas o rechazarlas.
  4. Mover el perfil a enforce.
  5. Repetir el ciclo cada vez que cambies la app o sus dependencias.

Para verificar que un proceso NO está confinado (o sea, que se nos escapó del inventario):

# Lista procesos sin perfil AppArmor (riesgo de escape)
sudo aa-unconfined --paranoid
# Equivalente leyendo /proc directamente
for pid in /proc/[0-9]*; do
  exe=$(readlink "$pid/exe" 2>/dev/null) || continue
  cat "$pid/attr/current" 2>/dev/null | grep -q '^/' && continue
  echo "${pid##*/} $exe"
done

Si aa-unconfined te muestra procesos corriendo como root que deberían estar confinados, ese es el próximo perfil a escribir. Para setups grandes puedes cruzar la lista con tu CM (Ansible, Puppet) y generar perfiles automáticamente desde el inventario de servicios.

Troubleshooting rápido: los errores que vas a ver

Tres patrones que se repiten siempre que tocas un perfil:

  • “Permission denied” al abrir un archivo después de enforce: el archivo existe pero el perfil no lo lista. Pasalo a complain, ejercé el flujo, y agrega la regla con aa-logprof. Casi nunca es un bug de AppArmor — es una regla faltante.
  • El binario arranca pero los childs explotan: el perfil usa ux (unconfined execute) o no tiene regla para el binario hijo. Cambiá a ix y creá un perfil para el hijo, o usa Px con su perfil dedicado.
  • Funciona en local pero falla en CI/contenedor: el contenedor o el CI no carga AppArmor (muchas imágenes Docker lo desactivan). verifica con cat /proc/1/attr/current; si devuelve unconfined, AppArmor no está activo en ese entorno y los perfiles no aplican.

Cuándo NO usar AppArmor

AppArmor no es bala de plata. Hay tres casos en los que te conviene mirar otra cosa:

  • Si tu distro default usa SELinux (RHEL, Fedora, Rocky, AlmaLinux, CentOS Stream): configurar AppArmor en estas distros significa dos MACs peleando por el mismo LSM hook. Es posible pero sumas complejidad sin ganancia. Mejor aprender SELinux con audit2allow y sus booleanos.
  • Si necesitas controles muy finos sobre inter-process communication (por ejemplo, Mediatek SELinux o políticas RBAC con atributos múltiples): SELinux tiene más capacidad expresiva aquí. AppArmor es más simple pero menos expresivo.
  • Si tus binarios cambian de path dinámicamente (por ejemplo, builds de CI que viven en /tmp/build-*/binario): AppArmor requiere reglas concretas o globs estables. SELinux con labels transitorios es más flexible para este caso.

Para la mayoría de sysadmins y homelabs en Debian/Ubuntu, AppArmor es la opción correcta: documentación extensa, perfiles stock para casi todos los daemons, sintaxis legible y un comando (aa-genprof) que resuelve el 80% del trabajo. Si quieres profundizar la comparativa con SELinux y cuándo conviene migrar de uno al otro, está el post SELinux enforcing paso a paso en Linux 2026.

Resumen

AppArmor es el MAC más amigable para Debian/Ubuntu y derivados. Lo que tienes que llevar del post:

  • Activa los paquetes apparmor-utils y apparmor-profiles-extra en cada servidor nuevo.
  • Dejá los perfiles stock en enforce. Si un paquete los trae en complain, observá una semana antes de promover.
  • Para binarios propios, arrancá con aa-genprof en complain, entrená con tráfico real, refiná con aa-logprof, recién entonces mové a enforce.
  • Monitoreá aa-unconfined --paranoid periódicamente — te dice qué procesos se te están escapando del inventario.
  • Si el proceso es crítico, combiná AppArmor con un WAF o un firewall de red: AppArmor aísla al binario, el firewall aísla al servidor.

El hardening de un servidor Linux no es un producto, es un conjunto de capas. AppArmor es una de las más baratas de operar una vez que tienes los perfiles base listos — el resto del camino lo cubren auditorías con Lynis, auditd para detectar cambios sospechosos, firewalls con nftables y aislamiento de procesos con Bubblewrap.

Preguntas frecuentes

¿Por qué Configuración de AppArmor para endurecer 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 Configuración de AppArmor para endurecer?

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 Configuración de AppArmor para endurecer?

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 Configuración de AppArmor para endurecer 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 Configuración de AppArmor para endurecer?

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 *