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
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
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/
Puntos Clave
|