You are opening our Spanish language website. You can keep reading or switch to other languages.
04.11.2024
4 minutos de lectura

La guía que necesitas para resolver problemas con dependencias en Java

Descubre en esta nota cómo analizar y solucionar problemas con dependencias en Java, utilizando la herramienta de building: Maven. ¡No te la pierdas!
La guía que necesitas para resolver problemas con dependencias en Java
Autores
Pablo Gutiérrez

En ambientes enterprise, es muy común que en el pipeline CI/CD se haga un escaneo de vulnerabilidades de seguridad sobre las dependencias de nuestros artefactos o sobre nuestro propio código. Muchas veces ese escaneo no permite completar la creación de nuestros artefactos.

En este artículo, te quiero mostrar cómo analizar y resolver problemas con dependencias en Java, utilizando la herramienta de building: Maven.

La seguridad es crítica en los sitios de cara al cliente: siempre están buscando el error más mínimo para vulnerar cualquier sitio que sea. Ahora bien, hay que contextualizar y saber qué puede hacer el atacante o el bug.

La mayoría de las veces estoy en desacuerdo con la severidad que les asignan a las vulnerabilidades. De esta forma, logran que nos genere más demoras en nuestros entregables por sobre la mitigación de los posibles ataques.

Si se detecta una vulnerabilidad en un backend, por ahí no tiene la misma “criticidad” que una de frontend. Pero las reglas o procedimientos de algunos pipelines nos pueden demorar un entregable y ahí necesitamos entender cómo resolver estos issues.

Acompáñame a repasar un poco lo que son las dependencias y su anidamiento al utilizar Maven.

Historia

En las primeras versiones de Java, las librerías se incluían directamente en un directorio del proyecto y el IDE las incluía en nuestro entregable. Luego surgió Apache Ant y, finalmente, Apache Maven.

Maven permite definir las dependencias de librerías explicitas en un XML. Por ejemplo, para incluir lombok agregamos:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.34</version>
</dependency>

Simple y fácil, ¿no? Si tengo una vulnerabilidad en esa versión, la cambio en el XML y listo.

El problema es que esas librerías o dependencias tienen sus propias dependencias. Además, con la popularidad de SpringBoot se hizo muy común el uso de “starters”, que son dependencias que definen dependencias, versiones y otros “starters”. Entonces, empezamos con nuestro árbol de dependencias.

Maven dependency:tree

Para ver este árbol de dependencias, por suerte Maven tiene el plugin dependency que nos ayuda a trabajar con las dependencias.

El comando mvn dependency:tree nos devuelve el listado de dependencias en forma de árbol visual.

Tip: si el proyecto no compila porque nos faltan dependencias️, este comando fallará. Debemos agregarle el --debug para armar la salida.

El problema es que los escaneos de vulnerabilidades nos dicen: “la librería xxxx versión yyyy tiene una vulnerabilidad”. Y los starters, al ser grupos de librerías, no tienen las mismas versiones.

En mi código de ejemplo, recién armado de start.spring.io veo esto:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.3</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

Súper simple, pero: ¿qué versión de logback-classic estoy usando? Apenas ejecuto mi dependency:tree, veo que es la versión 1.5.7.

[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.5.7:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.5.7:compile

¿Cómo llego de la versión 3.3.3 a la 1.5.7?  ¿Y si mi escaneo de vulnerabilidades me dice que la versión sin vulnerabilidad es la 1.5.8? ¿Cómo la cambio?

Maven exclusions

Por suerte en Maven hay un tag para excluir dependencias y agregar las versiones que necesitamos. Aquí el ejemplo:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
   <exclusion>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
   </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.5.8</version>
</dependency>

Al ejecutar nuevamente el dependency:tree podemos verificar que la versión ha cambiado. Listo, todos felices. Bueno... no realmente.

Dependencias de dependencias

Si en mi proyecto ejecuto el dependency:tree, veo esto:

[INFO] +- ch.qos.logback:logback-classic:jar:1.5.8:compile
[INFO] |  +- ch.qos.logback:logback-core:jar:1.5.7:compile
[INFO] |  \- org.slf4j:slf4j-api:jar:2.0.16:compile

Mi logback-core sigue siendo la 1.5.7 y puede que mi escaneo me diga otra dependencia por cambiar. Así que este proceso puede llevarnos varias veces sobre lo mismo.

Esto es así. Porque cuando hago una exclusión, solo hago exclusión de ESE artefacto en específico, no los de que este depende (que pueden estar definidos en otra parte).

Muchas veces, vemos la definición en el dependencyManagement en un par de clics y sabemos dónde se está definiendo. Sin embargo, a veces es más rebuscado: sobre todo con código legacy.

Para ver qué POM es el que está definiendo un artefacto, podemos ejecutar el siguiente comando:

mvn help:effective-pom -Dverbose -Doutput=efective-pom.xml

Tip: el parámetro verbose, me agrega el comentario del archivo que define esa dependencia.

Ese comando me generará un archivo xml con la referencia de donde sale esa definición. En mi ejemplo sería:

<dependency>
  <groupId>ch.qos.logback</groupId>  <!-- org.springframework.boot:spring-boot-dependencies:3.3.3, line 1215 -->
  <artifactId>logback-core</artifactId>  <!-- org.springframework.boot:spring-boot-dependencies:3.3.3, line 1216 -->
  <version>1.5.7</version>  <!-- org.springframework.boot:spring-boot-dependencies:3.3.3, line 1217 -->
</dependency>

Así, puedo ver que en spring-boot-dependenies está definido de la siguiente manera:

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>${logback.version}</version>
</dependency>

Por suerte hay una property que define ese valor. De esta forma, podría redefinir la property en mi pom y me voy a la versión 1.5.8 sin hacer los exclusions.

<logback.version>1.5.8</logback.version>

Y corriendo el dependency:tree efectivamente se actualizó la versión a 1.5.8:

[INFO] +- ch.qos.logback:logback-classic:jar:1.5.8:compile
[INFO] |  +- ch.qos.logback:logback-core:jar:1.5.8:compile
[INFO] |  \- org.slf4j:slf4j-api:jar:2.0.16:compile

Conclusión

Con Maven hay un montón de utilidades, plugins Maven – Available Plugins y parámetros que nos pueden ayudar. Aconsejo revisar la documentación para ver cómo nos puede ayudar en nuestro problema.

Más buscadas
1 3
Suscribirse al newsletter
Suscríbete a nuestro newsletter para no perderte ningún evento, anuncio o vacante disponible