DEV Community

Carlos García
Carlos García

Posted on

Incrementar seguridad de Dockerfiles quitando privilegios de Root en los contenedores

Cuidar nuestra seguridad al momento de crear Dockerfiles es igual de importante que todas las demás acciones implementadas para fortalecer la seguridad informática de nuestras aplicaciones.

Hace un par de días me llamó la atención un artículo sobre seguridad informática enfocado a la configuración y buen uso de la herramienta de Docker. Y aunque no lo había pensado, tiene mucho sentido, muchas veces nos dejamos llevar por los ejemplos que encontramos en línea y no nos damos cuenta que están escritos con mucha simpleza por la misma naturaleza del "tutorial", pero meter este código simple a nuestros ambientes productivos resulta ser un peligro porque la mayoría de las veces pasa por alto la atención al detalle que deberíamos de tener para poder evitar código vulnerable.

Los ejemplos de la siguiente guía están pensados para proyectos de Net Core pero son perfectamente trasladables a cualquier otro tipo de proyecto.

Dockerfiles

Haciendo un poco de investigación al respecto de cómo disminuir el tamaño de las imágenes que utilizo en mis proyectos de Go, me encontré con varias páginas hablando al respecto de las buenas prácticas que debemos de tener al momento de escribir nuestros Dockerfiles.

Dentro de las practicas mencionaban asignar la menor cantidad de privilegios posibles a nuestros contenedores Docker. Resulta que, por defecto, al momento de ejecutar un proceso dentro de un contenedor, Docker ejecuta dicho proceso con el usuario root. ¿Cómo? Sí, Docker le asigna los permisos de root a todos los procesos que corran dentro de un contenedor.

Docker y el Kernel

Antes de continuar, debemos de refrescar un concepto básico de Docker: Docker gana rendimiento porque no es una máquina virtual, simplemente es una herramienta para correr procesos específicos dentro de contenedores y, así poder tener un mejor manejo de dependencias y asegurarnos de que lo que funciona en nuestro sistema local, también funcionará en nuestro servidor.

Estos contenedores comparten el Kernel con la máquina Host.

Debido a que el Kernel es el encargado de la administración de los uid y gid (User Id y Group Id) los usuarios dentro de los contenedores tendrán los mismos privilegios afuera de los mismos. Un proceso ejecutado por root dentro de un contenedor Docker hereda los mismos privilegios que un usuario root en el Host.

Esto es peligroso porque expone la información confidencial que tengamos en nuestra máquina virtual en la nube (o peor, nuestro servidor on-premise) y abre la puerta a que cualquier atacante puedan escalar a privilegios root después de haber ganado acceso a cualquier contenedor con privilegios de root. ¿Se acuerdan de la historia de terror de David Gilberson? (https://medium.com/hackernoon/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5) ¿Qué pasaría si incluyéramos una dependencia así en alguno de nuestros contenedores con privilegios de root?

La prueba

Si ustedes crean un contenedor (de una imagen de desarrollo en la que se use directamente el usuario root) tienen varias formas de comprobar cuales privilegios son los que tiene el proceso corriendo en el contenedor.

Lo primero que pueden hacer es entrar a la consola interactiva y preguntar por el usuario actual. Podemos hacer esto con el comando whoami o el comando id -u, la salida debería ser la siguiente:

$ docker exec -it <idContenedor> /bin/bash
$ whoami
root
$ id -u
0
Enter fullscreen mode Exit fullscreen mode

También podemos ver los procesos que se están ejecutando en un contenedor en específico sin tener que entrar a la consola interactiva:

$ docker top <idContenedor>
Enter fullscreen mode Exit fullscreen mode

La salida del comando anterior nos dará el id del proceso que se está ejecutando dentro del contenedor así como el nombre del usuario que lo ejecutó.

Nota: en los enlaces que puse al final del texto se usan comandos como ps, pero al intentar usarlos en la consola interactiva de la imagen de .Net nos damos cuenta que el comando no está disponible.

La solución

La solución inmediata para remediar este problema es crear un usuario y asignarlo al contenedor al momento de crearlo, esto siendo en el Dockerfile. Las siguientes lineas nos ayudaran a hacerlo:

RUN groupadd -r nombreGrupo && useradd -r -g nombreGrupo nombreUsuario
USER nombreUsuario
Enter fullscreen mode Exit fullscreen mode

Los comandos para crear usuarios y grupos son distintos entre las diferentes distribuciones Linux que usemos como base en nuestro Dockerfile.

Agregando estas dos lineas en el stage final del Dockerfile, antes de ejecutar el ENTRYPOINT, nos permitirá usar este usuario en vez de root.

Kestrel y aplicaciones Non Root

Después de hacer estos cambios e intentar correr el proyecto, nos daremos cuenta el proyecto no va a poder arrancar.

El problema aquí es que aun que el puerto por default que usa Kestrel para servir nuestra api durante el desarrollo es el 5000, al momento que incluimos la imagen mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim (runtime de net core) de base en nuestro Dockerfile, seteamos inconscientemente el puerto 80 como el designado para nuestra aplicación.

Lo único que necesitamos hacer para remediar lo anterior es:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
Enter fullscreen mode Exit fullscreen mode

Justo después de hacerle pull a la imagen de runtime que usamos para nuestro proyecto de Net Core.

Lo único que queda por hacer, sería routear el puerto 8080 del host con el 5000 del container, esto dentro del docker-compose.yml

version: '3.7'
services:
    api:
        ...
        ...
        ports:
            - "8080:5000"
Enter fullscreen mode Exit fullscreen mode

La lección

Así como revisamos la precedencia de las dependencias que usamos en nuestros proyectos, también debemos de informarnos acerca de las imágenes que usamos como base para nuestros proyectos ya que no todas están preparadas para subirse a producción. Es por lo anterior que siempre tenemos que revisar qué es lo que hacen estas imágenes base y hacer nuestro Dockerfile multistage para poder definir bien la imagen que correrá en nuestro ambiente productivo.

Enlaces de referencia


Running a Docker container as a non-root user

Best practices for writing Dockerfiles

Kestrel web server implementation in ASP.NET Core

Processes In Containers Should Not Run As Root

Docker user best practices

Docker Tips: Running a Container With a Non Root User

Understanding how uid and gid work in Docker containers

Discussion (0)