Construyendo imágenes seguras con Docker

Juan Manuel Peña Uribe | 04 de noviembre, 2020

Apoyado por nuestro experto en innovación Josué Murillo

Muchas veces cuando empezamos a desarrollar microservicios y a construir nuestras imágenes Docker utilizamos los Dockerfile por defecto, los cuales crean las imágenes Docker utilizando el usuario root. Esto puede ser explotado como vulnerabilidad ya que al ejecutarse como usuario root se tiene acceso a todo el contenedor y modificar sus recursos. Además, se puede inyectar código malicioso que ejecute comandos shell o montar volúmenes con los permisos de root.

La solución a esto es crear nuestras imágenes Docker y setear un usuario diferente a root, de preferencia que no tenga permisos de crear o modificar los recursos del contenedor. En caso que se requiera montar un volúmen y escribir sobre el volúmen, se debe especificar una ruta destinada en el contenedor para realizar dicha operación.

Ahora que ya explicamos la teoría, veamos una aplicación práctica. Es importante que tengas una máquina virtual con docker instalado para aplicar este ejemplo práctico, todas las fuentes que se usarán en este ejemplo lo dejaré en mi repositorio github.

Para este ejemplo práctico, he creado una aplicación Java en Micronaut el cual es un JAR para ser construido por el Dockerfile. Esta aplicación escribe log para probar los permisos asignados.

Para la prueba creamos nuestro contenedor con un Dockerfile por defecto con el usuario root por defecto.

Ejecutamos el comando:

docker build -t javawithroot

 

Ejecutamos nuestro contenedor con el comando:

docker run -d -p 8099:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithroot

La aplicación java demo utiliza logback y como variable de entorno para el PATH del log utiliza la variable “HOME_LOG” por lo cual se setea al momento de ejecutar como environment (para este ejemplo la ruta “/opt/logs”). También montamos un volumen relacionado a la ruta “/opt/logs” en el cual guardaremos nuestros logs de aplicación.

Ahora ingresamos por SH al contenedor, y una vez que ingresamos al contenedor ejecutamos “ps” y observamos que los procesos se estan ejecutando con el usuario root

Para ingresar por SH a nuestro contenedor digitamos:

docker exec -it 2228fae46db9d5 /bin/sh

Donde 2228fae46db9d5 es el ID de tu contenedor cuando lo ejecutaste

 

Con el usuario root podemos ver que podemos crear carpetas o manipular ficheros dentro del contenedor.

Si verificamos nuestra ruta “/opt/logs” veremos que se creo el log con el usuario root:

Ahora procederemos a crear nuestra imagen Docker con el Dockerfile que tiene seteado un usuario distinto a root y permisos con menos privilegios

Construimos la imagen con el comando:

docker build -t javawithoutroot

Ahora ejecutamos el contenedor con los mismos parametros que ejecutamos anteriormente:

Para ejecutar nuestro contenedor ejecutamos el comando:

docker run -d -p 8098:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithoutroot

Debemos apuntar el ID de ejecución del contenedor.

 

Si ingresamos a ver el log como contenedor, vemos que ha dado un error de permisos al momento de intentar grabar el log:


Este error se produce porque la ruta “/opt/logs” en el sistema principal no tiene los permisos necesarios para que sea escrito.

Para ingresar por SH a nuestro contenedor digitamos:

docker exec -it c06036b4d01db9 /bin/sh

Donde c06036b4d01db9 es el ID de tu contenedor cuando lo ejecutaste

 

Una vez que ingresamos por SH al contenedor, observamos que los procesos dentro del contenedor ya no se ejecutan con el usuario root. Si intentamos crear una carpeta, se mostrará un mensaje de permiso denegado.

Para superar el problema para que grabe en la ruta “/opt/logs”, debemos brindar en el sistema principal el mismo nivel de permiso que se da en la construcción de la imagen.

Digitamos el comando:

chown 1001:1001 -R /opt/logs/

 

Para validar que el problema este superado debemos realizar los siguientes pasos:

1.Detener primero nuestro contenedor con el comando:
docker stop c06036b4d01d
 Donde “c06036b4d01d” es el ID de ejecución del contenedor.
 
2.Ejecutar nuestro contenedor con el siguiente comando y anotar el ID de ejecución del contenedor:

docker run -d -p 8098:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithoutroot

 

3.Verificar que no se presente de nuevo el problema con el comando:

docker logs -f 3d9bb9b5561092b
 Donde “f 3d9bb9b5561092b” es el ID de ejecución del contenedor, con esto validamos que no se vuelva a presentar el problema de permisos.
 

4.Ingresamos a la ruta “/opt/logs” y verificamos que se haya creado el log; con esto damos por solucionado el problema.

 

Recomendaciones

Como siempre, las fuentes del ejemplo utilizado se encuentra en mi repostitorio github.

Al seguir el ejemplo visto en este artículo, podemos crear aplicaciones más seguras para que las vulnerabilidades no sean explotadas por posibles ataques. Una recomendación es que, además de asegurar nuestras imágenes Docker, si estamos en un entorno Kubernetes apliquemos políticas de seguridad con la finalidad de complementar la seguridad de tu cluster. https://kubernetes.io/docs/concepts/policy/pod-security-policy/

idea

Puntos Clave

  1. Crear imágenes Docker con el usuario root puede convertirse en una vulnerabilidad debido a la libertad de permisos que tiene este usuario.
  2. Este problema se puede evitar al crear imágenes Docker utilizando un usuario distinto, uno que no tenga permisos para crear o modificar los recursos del contenedor.
  3. En caso que se requiera montar un volúmen y escribir sobre el volúmen, se debe especificar una ruta destinada en el contenedor para realizar dicha operación.

Contáctenos

Contenido

Categorías

Compartir Artículo

Artículos Destacados