AppArmor y Seccomp: perfiles de seguridad del kernel
En el capítulo de RuntimeClass vimos que el riesgo de fondo de los contenedores es el kernel compartido. Antes de llegar al extremo de los sandboxes, Linux trae de serie dos mecanismos para reducir lo que un contenedor puede pedirle al kernel: seccomp (filtra syscalls) y AppArmor (control de acceso obligatorio). Kubernetes los integra en el securityContext, y el CKS los pregunta sí o sí.
La foto completa de capas, de dentro hacia fuera:
Capabilities: el repaso necesario
Las capabilities trocean los privilegios de root en piezas (CAP_NET_ADMIN, CAP_SYS_TIME, CAP_CHOWN...). Ya las vimos en el securityContext; la doctrina es simple y cae en todos los exámenes:
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # Solo si de verdad la necesita
Suelta todas, añade la imprescindible. Con eso claro, vamos a las dos piezas nuevas.
Seccomp: filtrar syscalls
Seccomp (secure computing mode) filtra qué llamadas al sistema puede hacer un proceso. Linux tiene más de 300 syscalls y un contenedor típico usa unas decenas: todo lo demás es superficie de ataque gratuita.
RuntimeDefault: el 80% del valor con una línea
Los runtimes traen un perfil por defecto sensato (bloquea unas 60 syscalls peligrosas: mount, reboot, init_module...). Kubernetes no lo aplica salvo que se lo pidas:
apiVersion: v1
kind: Pod
metadata:
name: pod-seccomp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
Esto debería ser el mínimo de todos tus pods (el nivel restricted de los Pod Security Standards lo exige). Los tres tipos posibles:
RuntimeDefault: el perfil del runtime. Tu valor por defecto.Unconfined: sin filtro (el comportamiento histórico, evitar).Localhost: un perfil personalizado cargado desde el nodo.
Perfiles personalizados
Para cargas críticas se puede afinar más con un perfil propio en JSON, ubicado en el nodo bajo /var/lib/kubelet/seccomp/:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "exit", "exit_group", "futex", "nanosleep"],
"action": "SCMP_ACT_ALLOW"
}
]
}
defaultAction: SCMP_ACT_ERRNO = denegar por defecto y permitir solo la lista. Se referencia con ruta relativa a ese directorio:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/mi-perfil.json # /var/lib/kubelet/seccomp/profiles/mi-perfil.json
Dos gotchas de examen: el fichero debe existir en el nodo donde se programa el pod (en todos, en la práctica), y si el perfil bloquea una syscall que la app necesita, el contenedor morirá al instante o ni arrancará: los logs y dmesg del nodo lo delatan.
AppArmor: qué recursos puede tocar el proceso
AppArmor es un sistema MAC (Mandatory Access Control): perfiles que definen qué puede hacer un programa con ficheros, red y capabilities, independientemente de los permisos clásicos. Donde seccomp dice "qué syscalls", AppArmor dice "sobre qué recursos".
Los perfiles viven en el nodo (/etc/apparmor.d/) y se gestionan con sus herramientas:
# Estado y perfiles cargados en el nodo
sudo aa-status
# Cargar un perfil
sudo apparmor_parser -r /etc/apparmor.d/mi-perfil
Un perfil de ejemplo que impide toda escritura en disco:
#include <tunables/global>
profile deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Denegar escritura en todo el sistema de ficheros
deny /** w,
}
Aplicarlo a un pod
Desde Kubernetes 1.30, AppArmor es un campo de primera clase del securityContext (antes era una annotation, que aún verás en material antiguo):
apiVersion: v1
kind: Pod
metadata:
name: pod-apparmor
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: deny-write # Nombre del perfil cargado en el nodo
Los tipos son análogos a seccomp: RuntimeDefault, Localhost y Unconfined.
# Verificarlo desde dentro
kubectl exec pod-apparmor -- cat /proc/1/attr/current
# deny-write (enforce)
kubectl exec pod-apparmor -- touch /tmp/test
# touch: /tmp/test: Permission denied <- el perfil funcionando
Mismo gotcha que seccomp: si el perfil no está cargado en el nodo, el pod se queda en estado Blocked/error con un evento que lo explica.
¿Y SELinux? Es el equivalente en el mundo Red Hat (campo
seLinuxOptions). Para el CKS basta con saber ubicarlo; los ejercicios usan AppArmor porque los nodos del examen son Ubuntu.
Estrategia práctica
- Base universal:
seccompProfile: RuntimeDefault+capabilities: drop ALL+runAsNonRoot+allowPrivilegeEscalation: false. Esto es el nivelrestrictedde PSS y no rompe el 95% de las aplicaciones. - Cargas sensibles: perfiles seccomp/AppArmor personalizados, generados a partir de observar la app (herramientas como
inspektor-gadgeto el propio Falco ayudan a ver qué syscalls usa realmente). - Hazlo obligatorio: con Pod Security Admission o las políticas de admisión, para que nadie despliegue sin perfil.
Resumen
- Capas complementarias: capabilities (privilegios) → seccomp (syscalls) → AppArmor (recursos). Defensa en profundidad, no alternativas.
seccompProfile: RuntimeDefaultdebería ser el mínimo universal;Localhostpara perfiles JSON propios en/var/lib/kubelet/seccomp/.- AppArmor se configura ya en
securityContext.appArmorProfile; el perfil debe estar cargado en el nodo (apparmor_parser,aa-status). - Verificación:
/proc/1/attr/current(AppArmor) y probar operaciones prohibidas; los fallos de perfil aparecen en eventos del pod.
- Lista de vídeos en Youtube: Curso Kubernetes