Detecting vulnerabilities inside our dependencies is crucial for creating robust, reliable, and secure applications. Besides that, static code analysis tools and pre-defined rules can help us maintain a healthy and qualitative code base. Fortunately, there are Maven plugins available to automate this within our build. With this blog post, I'll demonstrate my top three Maven plugins to ensure both quality and security for Java projects.
Maven Project to Demonstrate the Plugins
To see these three Maven plugins in action I've created a Spring Boot Maven project using Java 11 and the following dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.rieckpil.blog</groupId> <artifactId>maven-plugins-to-ensure-quality</artifactId> <version>0.0.1-SNAPSHOT</version> <name>maven-plugins-to-ensure-quality</name> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project> |
Besides the standard Java class to start the Spring Boot application, the project consists of one Java class to read a file and print the output on startup:
1 2 3 4 5 6 7 8 9 10 | @Component public class BadPracticeFileReader implements CommandLineRunner { @Override public void run(String... args) throws Exception { InputStream in = this.getClass().getResourceAsStream("/message.txt"); byte[] allBytes = in.readAllBytes(); System.out.println(new String(allBytes)); } } |
This class acts as a negative example to demonstrate the usage of one Maven plugin later on.
Maven Enforcer Plugin
The Maven Enforcer plugin enables us to check several pre-defined rules for our project. This can be as simple as checking the Java, Maven, or OS version during build time (find a list of all rules here).
Similarly, we can use this plugin to blacklist a set of dependencies that are banned from our project. A good use case for this might be after the transition from JUnit 4 to JUnit 5. Once all our tests are using JUnit 5, we can exclude the JUnit 4 dependencies and create a rule so that other teammates might not include it again by accident (and potentially mix JUnit 4 and 5, which is one of the most common Spring Boot testing pitfalls).
Next, we can use the dependencyConvergence
rule from the Maven Enforcer plugin to ensure there is only one version of a dependency available in our project. Once our project grows and includes more dependencies, there might be two dependencies pulling in a different version of Jackson, for example. This can lead to issues during runtime whenever we rearrange our dependencies, update the versions or remove dependencies (watch this video for a deep dive on how Maven handles dependency version conflicts).
To detect such scenarios, the dependencyConvergence
rule will fail whenever there a transitive dependency is included with different versions. We can then fix this by either pinning the dependency version inside the dependencyManagement
or by excluding it from one dependency.
We add this plugin to our project like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <configuration> <rules> <bannedDependencies> <excludes> <exclude>junit:junit</exclude> <exclude>junit:junit-dep</exclude> </excludes> </bannedDependencies> <dependencyConvergence/> </rules> </configuration> <executions> <execution> <goals> <goal>enforce</goal> </goals> </execution> </executions> </plugin> |
Once we run mvn verify
or mvn enforcer:enforce
, our configured rules checked. If one of the rules fail, our build is stopped, and Maven reports a build failure.
OWASP Dependency-Check to ensure dependency quality
The more dependencies we add to our project, the more likely we have to handle Common Vulnerabilities and Exposures (CVE). Manually checking the CVE database for new issues is cumbersome.
Fortunately, there is a Maven plugin to do this during build time: OWASP Dependency-Check Maven plugin. This plugin analyzes all our dependencies and either fail the build (if configured) or produce log warnings once one of our dependency is part of a CVE.
Please note that the vulnerabilities are downloaded from the National Vulnerability Database (NVD) hosted by NIST: https://nvd.nist.gov. This might take some minutes for the first run, and if we plan to use it, we should definitely enable caching for our CI/CD pipeline (example for GitHub Actions here). In the past, we also observed some downtime of this hosted vulnerability database. In such cases, we configured the plugin to not fail the built.
Furthermore, there might be vulnerabilities that occur just for a specific dependency setup or operating system. To suppress such false positives, we can add a owasp-suppressions.xml
file to our project and exclude these CVEs:
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0" encoding="UTF-8"?> <suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd"> <!-- example to suppress a false warning <suppress> <notes><![CDATA[ Suppress all dependencies for CVE-2018-1258 (https://pivotal.io/security/cve-2018-1258) ]]></notes> <cve>CVE-2018-1258</cve> </suppress> --> </suppressions> |
Including the plugin is as simple as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>7.1.1</version> <configuration> <suppressionFiles> <suppressionFile>${project.basedir}/owasp-suppressions.xml</suppressionFile> </suppressionFiles> <failBuildOnCVSS>8</failBuildOnCVSS> <assemblyAnalyzerEnabled>false</assemblyAnalyzerEnabled> <failOnError>true</failOnError> </configuration> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> |
With the failBuildOnCVSS
configuration value, we specify the severity of a CVE to fail the build. The score ranges from 0 to 10.
Once we run mvn verify
or mvn dependency-check:check
our dependencies are analyzed.
SpotBugs Maven Plugin to ensure code quality
Having a static code analysis tool might help fixing potential bugs. Besides SonarQube there is a popular Maven plugin that does not need further setup: SpotBugs Maven plugin (formerly known as FindBugs).
We can include this Maven plugin with the following configuration:
1 2 3 4 5 6 7 8 9 10 11 12 | <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <version>4.7.1.0</version> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> |
Now we can run mvn spotbugs:check
to analyze our source code. Furthermore, we can launch a simple UI to analyze potential code analysis warnings with mvn spotbugs:gui
.
In our example, SpotBugs complains about the implementation of the BadPracticeFileReader
class. It detects three potential bugs as we use the default encoding while creating the String out of the byte array and don't properly clean the opened resource.
Having a static analysis tool is a great way to detect low-hanging bugs early on. For a more sophisticated code analysis, take a look at SonarCloud. For immediate feedback, there's SonarLint, a free IntelliJ IDEA plugin.
The source code for this article is available on GitHub.
You can find further Maven-related blog posts:
- Maven Setup For Testing Java Applications
- Introduction to GitHub Actions for Java And Maven Projects
- Run Java Tests With Maven Silently (Only Log on Failure)
Have fun using these Maven plugins to ensure the quality of your project,
Phil