Traditional Java stacks were engineered for monolithic applications with long startup times and large memory requirements. In today's world powered by cloud, containers, and Kubernetes, Java frameworks have evolved to meet these new requirements.
Modern frameworks such as Quarkus enable Java developers to write applications for a cloud-native world. Their goal is to make Java the leading platform in Kubernetes and serverless environments while offering developers a framework to address a wider range of distributed application architectures. These frameworks can be used to create microservices-based applications written in Java that run in Red Hat OpenShift and serverless environments.
[ Download A Java developer's guide to Quarkus. ]
Applications compiled to native executables have small memory footprints and fast startup times. When you are trying to achieve a faster startup time and smaller memory footprint, it's unlikely the framework will have all the artifacts confined to a zip archive. Which artifacts the user application's
pom.xml (Project Object Model) file requires is determined by an Apache Maven dependency mechanism and pulled from the configured Maven repository.
Since all these artifacts depend on the user's application, managing them with all the dependencies is crucial from a security point of view.
What are dependencies?
Imagine you are writing some business code to manage logging. You can implement this logic in the project or use a library, such as
slf4j. It often makes sense for Java developers to use existing libraries that solve similar problems. This practice minimizes the amount of code developers need to write and encourages reuse. These libraries are the application's dependencies.
Maven has two types of dependencies:
- Direct dependencies: These dependencies are explicitly included in a
pom.xmlfile in the <dependencies> section.
- Transitive dependencies: A project included as a dependency in a larger project can declare its own dependencies in a
pom.xmlfile. These dependencies are then considered transitive dependencies to the primary project. When Maven pulls a direct dependency, it also pulls its transitive dependencies.
[ Learn more about how to install, configure, and use Maven. ]
What is the problem?
For multimodule projects and applications with hundreds of modules, it is very common to have a dependency on multiple versions of the same artifact. Determining and controlling the dependencies and their versions in such projects is not easy because they involve multiple factors, like which version is deeper (Dv 2.0), which one is nearer (Dv 1.0), and which comes earlier (Dv 2.0) in the dependency tree.
A ├── B │ └── C │ └── Dv 2.0 ├── E │ └── Dv 3.0 │ └── Dv 1.0
Maven has its own mechanism to determine what dependency version should be pulled. Refer to Maven's Dependency Mechanism to learn more about how it works. Even the sequence in which the
pom.xml file lists dependencies matters in this mechanism.
The Maven repository often makes multiple versions of many artifacts available. Since the dependencies and versions to pull rely entirely on the user application's
pom.xml file, you must have something to guide Maven to pull the newer and safer versions of the dependencies.
Scan dependencies using Maven's plugin
One part of the problem is identifying the safer versions of dependencies. You can address this by using a Software Composition Analysis (SCA) tool to help detect publicly disclosed vulnerabilities within a project's dependencies. OWASP Dependency-Check can be used to scan applications (and their dependent libraries) to identify known vulnerable artifacts.
OWASP Dependency-Check also provides a
dependency-check-maven plugin that uses
dependency-check-core to detect publicly disclosed vulnerabilities associated with the project's dependencies. The plugin will generate a report listing the dependency, any identified Common Platform Enumeration (CPE) identifiers, and the associated Common Vulnerability and Exposure (CVE) entries.
[ Learn how IT modernization can help alleviate technical debt. ]
The Dependency-Check plugin can be configured inside the plugin section in your project XML file, as shown below :
<project> … <build> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>7.1.2</version> <executions> <execution> <goals <goal>check</goal> </goals> </execution> </executions> </plugin> <build> ... </project>
Control dependencies using Maven's BOM
The Bill of Materials (BOM) is a special POM file that groups dependency versions that are secured and tested to work together. This can minimize the developers' pain of having to test different versions' compatibility and reduce the security risk. The BOM file has a dependencyManagement section that lists the dependencies and their versions to be used in a project.
<project ...> <modelVersion>2.0</modelVersion> <groupId>example</groupId> <artifactId>example-bom</artifactId> <version>1.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>io.example</groupId> <artifactId>D</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>io.example</groupId> <artifactId>E</artifactId> <version>3.0</version> </dependency> </dependencies> </dependencyManagement> </project>
Vendors need to compile these BOM files very carefully to manage versions of the important dependencies and help the customer application pull only the safer versions of any dependency. It's recommended that users use these BOM files in their projects so that their applications pull the safer and more compatible versions of dependencies.
You can use this BOM file in their projects in two ways :
1. As a parent POM
A BOM file can be used as a parent POM of a new project. This newly created project will inherit the dependencyManagement section from the BOM, and Maven will use it to resolve all the dependencies required for the project.
<project ...> <modelVersion>3.0</modelVersion> <parent> <groupId>example</groupId> <artifactId>example-bom</artifactId> <version>1.0</version> </parent> <groupId>user-project</groupId> <artifactId>new-project</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>New Project</name> <dependency> …. </dependency> </project>
2. As a dependency
Developers can add a BOM to a new project's POM file by editing the dependencyManagement section as a dependency with a POM type:
<project ...> <modelVersion>3.0</modelVersion> <groupId>user-project</groupId> <artifactId>new-project</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>New Project</name> <dependency> … </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>example</groupId> <artifactId>example-bom</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
Dependency management is crucial, especially with non-zip distributions where Maven fetches dependencies based on the user applications. Using the BOM is a good way to support security and consistency between dependency versions.
[ Check out Red Hat's Portfolio Architecture Center for a wide variety of reference architectures you can use. ]