Introducción

Un contenedor con readOnlyRootFilesystem: true puede parecer blindado, pero esa protección se evapora en el instante en que el pod monta un emptyDir sin flags de seguridad. El proceso comprometido escribe un binario en /tmp, le aplica chmod +x y lo ejecuta sin que kubelet ni el runtime intervengan. Kubernetes v1.37 introduce dos capacidades —VolumeBindMountOptions y EmptyDirVolumeMode— que eliminan ese vector de ataque a nivel de kernel, sin sidecars, sin init containers ni workarounds de AppArmor. Ambas funcionan como feature gates Alpha y requieren activación explícita en el API server y en cada kubelet.

Qué ocurrió

Hasta v1.36, el runtime de contenedores (containerd, CRI-O) montaba cualquier volumen dentro del contenedor mediante un bind mount clásico, sin transmitir flags VFS como MS_NOEXEC, MS_NOSUID o MS_NODEV. El campo mountOptions de un PersistentVolume se aplicaba a nivel del driver CSI en el nodo, pero no se propagaba al bind mount que el runtime creaba para el contenedor. El resultado: un volumen PersistentVolume o un emptyDir quedaba efectivamente ejecutable, sin importar la política de seguridad del pod.

El problema se agravaba con emptyDir, el volumen escribible más usado en Kubernetes. El kubelet creaba el directorio subyacente con modo 0777 fijo. Cualquier proceso que descubriera la ruta del volumen —un sidecar, un debug container, un proceso comprometido— podía leer, modificar y eliminar archivos de otros contenedores del mismo pod. No existía una forma nativa de declarar permisos restrictivos; la única alternativa era un init container con chmod, difícil de auditar en compliance y frágil ante cambios de imagen.

La propuesta llegó vía KEP-5855 (SIG Node) y KEP-5502 (SIG Storage), aprobadas para el ciclo 1.37. El objetivo explícito: que un desarrollador o un equipo de seguridad pueda declarar en el manifest del pod que un volumen no permite ejecución, no respeta bits SUID y no interpreta dispositivos, y que un emptyDir comparta con el modo sticky bit de /tmp las mismas garantías de aislamiento entre usuarios.

Impacto para DevOps / Infraestructura / Cloud / Seguridad

El escenario de mayor riesgo es un workload con readOnlyRootFilesystem: true que monta un emptyDir en /tmp para caché o artefactos de build. Sin noexec, un atacante que logre RCE en la aplicación descarga un reverse shell en ese directorio, le pone permisos de ejecución y lo lanza. El flag readOnlyRootFilesystem no cubre montajes adicionales: solo protege la capa raíz del contenedor.

Para equipos de SRE y platform engineering, la ausencia de control sobre permisos de emptyDir complicaba la auditoría. Un pod con tres contenedores compartiendo un scratch space en modo 0777 permitía que un contenedor comprometido borrara los artefactos del CI, eliminara logs del sidecar o corrompiera datos temporales de otro servicio. En pipelines de CI/CD con builders y loggers co-localizados, esto representaba un riesgo operativo concreto.

En entornos multi-tenant o regulados (PCI DSS, SOC 2), la imposibilidad de declarar nosuid/nodev en volúmenes del pod obligaba a depender de políticas de Pod Security Admission a nivel de namespace o de herramientas como OPA/Gatekeeper. Con las nuevas opciones, la política se expresa inline en el manifest, lo que simplifica la verificación automatizada y reduce la superficie de error.

Detalles técnicos

Las dos features se activan como feature gates Alpha. En el API server se habilita VolumeBindMountOptions=true y EmptyDirVolumeMode=true; en cada kubelet se replican ambos flags. Sin esa activación, los campos nuevos en el manifest se ignoran silenciosamente.

Los flags disponibles para bindMountOptions son noexec, nosuid y nodev. Se aplican como flags de bind mount a nivel de kernel (MS_NOEXEC, MS_NOSUID, MS_NODEV), lo que significa que el enforcement ocurre en el VFS, no en una capa de usuario. Un archivo puede tener permisos 0755 en el volumen, pero el kernel rechazará execve() porque el montaje prohíbe ejecución.

Para emptyDir, el campo mode acepta valores octales estándar de Unix. El modo 01777 replica el comportamiento de /tmp: todos pueden escribir, pero solo el dueño del archivo (o root) puede borrarlo o renombrarlo. El modo 0750 restringe acceso al dueño y al grupo, denegando lectura y escritura a cualquier otro proceso del pod.

Ejemplo de manifest con ambos features:

apiVersion: v1
kind: Pod
metadata:
name: hardened-workload
spec:
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
containers:
– name: app
image: myapp:latest
volumeMounts:
– name: scratch
mountPath: /tmp
bindMountOptions:
– noexec
– nosuid
– nodev
volumes:
– name: scratch
emptyDir:
mode: 01777

Verificación post-despliegue:

# Intentar ejecutar un script en volumen con noexec
kubectl exec -it hardened-workload — sh -c \
‘echo «#!/bin/sh» > /tmp/pwn && chmod +x /tmp/pwn && /tmp/pwn’
# Resultado esperado: «Permission denied» (MS_NOEXEC a nivel kernel)

# Intentar borrar archivo de otro usuario en emptyDir con sticky bit
kubectl exec -it hardened-workload — sh -c \
‘rm -f /tmp/otro-archivo’
# Resultado esperado: «Operation not permitted» (sticky bit 01777)

El campo bindMountOptions es una lista de strings. No se acepta remount ni flags de filesystem (ro sigue controlándose con readOnly en el volumeMount). La interacción con CSI drivers todavía está en evaluación: si el driver ya aplica opciones a nivel de montaje, el kubelet debe evitar conflictos al aplicar el bind mount adicional.

Qué deberían hacer los administradores y equipos técnicos

En clústeres donde ya activaron las feature gates Alpha, auditen todos los emptyDir existentes. Identifiquen pods que monten scratch space en /tmp o /var/tmp y evalúen si necesitan noexec. En workloads de build (Jenkins agents, GitLab runners, Tekton tasks), agreguen bindMountOptions: [noexec, nosuid] al volumeMount del workspace compartido. En pods multi-contenedor donde un sidecar escribe logs y otro los consume, seteen mode: 01777 en el emptyDir para evitar que un contenedor comprometido borre los logs del otro.

En entornos de producción donde aún no activan features Alpha, preparen las políticas de Pod Security Admission para exigir noexec en volúmenes escribibles una vez que la feature madure a Beta. Documenten la restricción en las plantillas de Helm y los kustomizations internos: cualquier emptyDir que no declare mode explícito debería generar una alerta en el pipeline de despliegue.

Para equipos de seguridad, incorporen en sus checklists de hardening la verificación de que ningún volumen montado en un pod con readOnlyRootFilesystem: true carezca de noexec. El comando kubectl get pod -o jsonpath='{.spec.volumes[*].emptyDir}’ permite detectar emptyDir sin modo configurado en un namespace completo.

Conclusión

Kubernetes v1.37 no reemplaza a AppArmor, SELinux ni a Pod Security Standards, pero elimina la necesidad de workarounds para un problema que existía desde los primeros releases: volúmenes escribibles que permitían ejecución de código arbitrario. La combinación de noexec a nivel de bind mount y modos de permisos sobre emptyDir le da al desarrollador una herramienta declarativa, auditable y enforced por el kernel. El trabajo ahora está en activar las feature gates en los clústeres, migrar manifests y cerrar la brecha antes de que la feature llegue a Beta en 1.38.

Fuentes

  • https://kubernetes.io/blog/2026/09/16/kubernetes-v1-37-hardening-container-storage/
  • https://lwn.net/
  • https://www.fastly.com/blog

Deja una respuesta

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