12k
All articles

Cómo asegurar Docker en una máquina Linux

El acceso al grupo Docker otorga privilegios de root. Proteja Docker en Linux con el modo sin privilegios, reglas sudo, Podman, contenedores reforzados y una lista de auditoría.

OpenReplay Team
OpenReplay Team
Cómo asegurar Docker en una máquina Linux

Añadir un usuario al grupo docker le otorga acceso root a la máquina. No es “casi root”, sino root completo, porque cualquiera que pueda comunicarse con el socket de Docker puede montar el sistema de archivos del host en un contenedor y modificarlo.

Ejecutaste usermod -aG docker $USER para dejar de escribir sudo, y la propia guía de post-instalación de Docker te indicó que lo hicieras. La misma página de post-instalación de Docker Engine en Linux también advierte, en un recuadro que la mayoría pasa por alto, que pertenecer a este grupo equivale a ser root. Si todavía te estás familiarizando con los conceptos básicos, la guía para principiantes sobre imágenes y contenedores de Docker los explica. Este artículo demuestra el riesgo con un solo comando. Después repasa las soluciones, ordenadas según el nivel de protección que ofrecen, y termina con una checklist para auditar una máquina heredada.

Puntos clave

  • Cualquier miembro del grupo docker, y cualquier proceso que se ejecute como ese usuario, puede obtener una shell de root en el host con docker run --rm -it -v /:/host alpine chroot /host sh.
  • Docker en modo rootless ejecuta tanto dockerd como tus contenedores dentro de un user namespace. Así, si el daemon se ve comprometido o un contenedor logra escapar, el atacante termina con un UID sin privilegios en el host en lugar de root.
  • Una regla de sudoers restringida a /usr/bin/docker sigue siendo equivalente a root, pero exige una contraseña para usar Docker y registra cada uso.
  • Podman no tiene daemon y se ejecuta en modo rootless por defecto cuando lo usas como usuario normal. Su CLI es en gran medida compatible con la de Docker, así que alias docker=podman cubre la mayoría de los flujos de trabajo diarios.
  • En un host heredado, comprueba quién pertenece al grupo docker, qué usuario ejecuta dockerd, los permisos del socket y si el daemon escucha en el puerto TCP 2375.

¿Por qué el grupo de Docker otorga acceso root?

El grupo docker equivale a root porque el daemon de Docker se ejecuta como root y el grupo controla el acceso de escritura a su socket Unix. Poder escribir en ese socket significa poder ordenar a un proceso root que haga cualquier cosa. Si quieres entender el mecanismo subyacente, la guía de permisos de archivos en Linux explica cómo los permisos de grupo de un archivo determinan quién puede abrirlo. En el caso de Docker, el único detalle relevante es que el grupo docker tiene permiso de escritura sobre el socket.

Esta es la prueba:

docker run --rm -it -v /:/host alpine chroot /host sh

Este comando monta el / del host en el contenedor mediante un bind mount y luego ejecuta chroot sobre él. Con eso obtienes una shell de root sobre el sistema de archivos real del host. Desde ahí puedes editar /etc/shadow o /etc/sudoers, o añadir claves SSH en /root. No necesitas contraseña ni exploit alguno. Cualquier script, dependencia o extensión del editor comprometida que se ejecute con tu usuario puede hacer lo mismo. La documentación de Docker lo explica en la sección sobre la superficie de ataque del daemon de Docker.

Configurar Docker en modo rootless

El modo rootless ejecuta dockerd y todos los contenedores dentro de un user namespace perteneciente a tu cuenta, por lo que es la solución directa al problema del grupo docker. El socket se ubica en $XDG_RUNTIME_DIR/docker.sock en lugar de /var/run/docker.sock. RootlessKit crea el namespace. Los binarios auxiliares setuid newuidmap y newgidmap asignan el UID 0 del namespace a tu propio UID. Para los demás UID del contenedor, asignan un rango tomado de /etc/subuid y /etc/subgid. La red funciona mediante una pila de red en modo usuario, como slirp4netns, gvisor-tap-vsock o pasta.

Instala los requisitos previos y luego comprueba tus rangos de IDs subordinados. La documentación del modo rootless exige un rango de al menos 65.536 IDs subordinados para tu usuario, tanto en /etc/subuid como en /etc/subgid.

sudo apt-get install -y uidmap docker-ce-rootless-extras
grep ^$(whoami): /etc/subuid /etc/subgid

Desactiva el daemon de root y ejecuta la herramienta de configuración como usuario normal:

sudo systemctl disable --now docker.service docker.socket
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
sudo loginctl enable-linger $USER
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

La línea enable-linger mantiene el daemon en ejecución después de cerrar sesión. Cuando Docker rootless funcione, sal del grupo antiguo con sudo gpasswd -d $USER docker. La documentación oficial del modo rootless cubre otras distribuciones. Si después el cliente no logra conectarse al daemon, la guía para solucionar errores de “Docker daemon not running” explica cómo revisar el socket y systemd.

El modo rootless requiere user namespaces sin privilegios. Ubuntu 24.04 y versiones posteriores los restringen por defecto mediante AppArmor. El paquete apparmor de Ubuntu ya incluye el perfil que necesita rootlesskit, así que instalar docker-ce-rootless-extras con apt funciona sin pasos adicionales. En cambio, si instalas con el script get.docker.com/rootless, tendrás que añadir ese perfil manualmente. No desactives la restricción en todo el sistema con kernel.apparmor_restrict_unprivileged_userns=0. La página de solución de problemas del modo rootless cubre los demás casos de fallo.

¿Qué desventajas tiene el modo rootless?

Estas son las principales limitaciones frente a un daemon con privilegios de root:

LimitaciónAlternativa
Por defecto no se pueden usar puertos inferiores a 1024sysctl net.ipv4.ip_unprivileged_port_start=80, o un proxy inverso
--privileged no otorga privilegios reales en el hostSolo se aplican las capabilities dentro del user namespace
La red en modo usuario es más lenta que el driver bridgeEl driver experimental lxc-user-nic, o hacer benchmarks antes de pasar a producción
Los kernels anteriores a 5.11 necesitan fuse-overlayfsEl kernel 5.11+ admite overlay2 de forma nativa

La página de solución de problemas enumera algunas más, como la falta de soporte para AppArmor, checkpoints y redes overlay.

Usar sudo con una regla restringida

Exigir sudo para usar Docker, en lugar de pertenecer al grupo docker, es una solución intermedia cuando el modo rootless no es compatible con tu carga de trabajo. Sigue siendo equivalente a root, porque sudo docker puede ejecutar el mismo comando chroot. Lo que ganas es una solicitud de contraseña y una entrada en el registro de auditoría por cada invocación. Además, el código que se ejecuta silenciosamente con tu usuario ya no tiene acceso al socket.

sudo gpasswd -d alice docker
sudo visudo -f /etc/sudoers.d/docker
alice ALL=(root) /usr/bin/docker

Esta regla solo permite ejecutar el binario de Docker, no comandos arbitrarios. Puedes revisar su uso con journalctl _COMM=sudo. La documentación de post-instalación también describe un método basado en newgrp, que protege el acceso a Docker con una contraseña de grupo mientras el CLI sigue ejecutándose con tu propio usuario.

Migrar a Podman

Podman no tiene daemon y, cuando lo ejecutas como usuario normal, funciona en modo rootless por defecto. Los contenedores se ejecutan como procesos hijos de tu usuario, dentro de un user namespace y con las mismas asignaciones de /etc/subuid. Su CLI imita a la de Docker lo suficiente como para que la mayoría de los comandos funcionen sin cambios.

sudo apt-get install -y podman
alias docker=podman
podman run --rm -p 8080:80 docker.io/library/nginx

Las diferencias aparecen en los casos límite: los nombres de imagen completamente cualificados, el soporte de Compose y las herramientas que esperan un socket de Docker. Prueba tus scripts antes de migrar a todo un equipo.

Reforzar la seguridad del propio contenedor

Independientemente del daemon que utilices, limita lo que puede hacer el proceso dentro del contenedor. Cuatro ajustes cubren la mayor parte del riesgo:

  • un USER que no sea root
  • ninguna capability más allá de las que la aplicación necesita
  • un sistema de archivos raíz de solo lectura
  • ninguna escalada de privilegios mediante binarios setuid
FROM node:22-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
docker run -d \
  --cap-drop=ALL \
  --read-only --tmpfs /tmp \
  --security-opt no-new-privileges \
  -p 8080:8080 myapp

La instrucción USER del Dockerfile define el UID en tiempo de ejecución. --cap-drop=ALL elimina todas las capabilities. Si una aplicación realmente necesita alguna, puedes volver a añadirla con --cap-add. Esta aplicación escucha en el puerto 8080, así que no necesita ninguna. La combinación de --read-only con --tmpfs hace que solo /tmp sea escribible. no-new-privileges impide que los binarios setuid eleven privilegios. Todas estas opciones están documentadas en la referencia de docker run.

Auditar una máquina heredada

En un VPS configurado por otra persona, responde cuatro preguntas:

  • ¿Quién pertenece al grupo docker?
  • ¿Qué usuario ejecuta dockerd?
  • ¿Quién puede escribir en el socket?
  • ¿El daemon escucha en la red?
getent group docker
ps -eo user,cmd | grep [d]ockerd
ls -l /var/run/docker.sock
sudo ss -ltnp | grep -E ':2375|:2376'
systemctl cat docker | grep -- '-H'
grep -s hosts /etc/docker/daemon.json

Grupo docker: todos los nombres que muestra getent tienen acceso root.

Daemon: si ps muestra root, el daemon se ejecuta con privilegios de root.

Socket: los permisos deberían ser srw-rw---- root docker. Si tiene permiso de escritura para cualquier usuario, corrígelo de inmediato.

Red: un listener en el puerto 2375 significa que la API de Docker está expuesta sin TLS, y cualquiera que pueda alcanzar ese puerto tiene acceso root. Las versiones actuales de Docker Engine se niegan a arrancar con esa configuración, pero un engine antiguo en un host heredado podría seguir ejecutándola. Un listener TLS en el puerto 2376 es más seguro, aunque cualquiera que tenga las claves de cliente sigue teniendo acceso root. Sigue la guía para proteger el socket del daemon y migrar a SSH o a TLS mutuo.

Contenedores: revisa también si hay contenedores que monten el socket, ya que cada uno de ellos es una vía de acceso a root:

docker ps -q | xargs -r docker inspect \
  --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock

El acceso al socket de Docker es acceso root, sin matices. Empieza por ejecutar getent group docker en cada máquina que utilices. Después, migra a esos usuarios a Docker rootless o a Podman, o bien protege el socket con una regla de sudo que quede registrada.

Preguntas frecuentes

¿Cuál es la diferencia entre Docker rootless y userns-remap?

Con userns-remap, dockerd sigue ejecutándose como root y solo los contenedores se ejecutan con UID subordinados reasignados, como el usuario dockremap que se crea al establecer userns-remap en 'default' en /etc/docker/daemon.json. Esto no cierra la brecha del grupo docker: un miembro del grupo puede pasar --userns=host para desactivar la reasignación en un contenedor concreto y ejecutar el escape con chroot. El modo rootless, en cambio, ejecuta el propio daemon con tu usuario.

¿Pueden coexistir Docker rootless y Docker con privilegios de root en la misma máquina?

Sí. Los dos daemons usan sockets distintos: /var/run/docker.sock para el daemon con privilegios de root y $XDG_RUNTIME_DIR/docker.sock para el rootless. Al ejecutar dockerd-rootless-setuptool.sh install se crea un contexto de CLI llamado 'rootless' y se activa automáticamente. Usa docker context use default para dirigirte al daemon de root y docker context use rootless para volver al rootless. Mientras el daemon con privilegios de root siga en ejecución, cualquiera que pueda escribir en su socket seguirá teniendo acceso root.

¿Mis imágenes y contenedores existentes se transfieren a Docker rootless?

No. El daemon rootless mantiene su propio directorio de datos, por defecto ~/.local/share/docker, separado del directorio /var/lib/docker que usa el daemon de root. Las imágenes, contenedores y volúmenes creados con Docker con privilegios de root no son visibles para él. Vuelve a descargar las imágenes, o expórtalas con docker save desde el daemon de root e impórtalas con docker load en el rootless. Actualiza también las tareas de copia de seguridad que solo cubran /var/lib/docker.

¿Por qué los archivos escritos por contenedores rootless aparecen con propietarios extraños en el host?

Docker rootless asigna los UID del contenedor a UID del host. El usuario root dentro del contenedor se corresponde con tu propio UID, por lo que los archivos que escribe en un bind mount aparecen como tuyos. Cualquier otro UID del contenedor se asigna dentro de tu rango de /etc/subuid. Con un rango que empieza en 100000, el UID 1000 del contenedor se convierte en el UID 100999 del host, y tu usuario no puede editar esos archivos directamente. Ejecutar chown a 0:0 dentro de un contenedor devuelve la propiedad de los archivos a tu usuario.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.