Streamlining Vulnerability Management with Docker-Dependency-Check

Manuel Jiménez
BestSecret Tech
Published in
6 min readJul 5, 2024
Generated with DALL-E

As software development accelerates, managing dependencies and vulnerabilities has become critical. DependencyCheck’s recent update brought challenges like rate limits and schema changes, impacting CI/CD pipelines.

As software development accelerates, managing dependencies and vulnerabilities has become critical. Modern applications increasingly rely on external libraries to function, which can introduce security vulnerabilities if not properly managed. Ensuring these libraries are secure is challenging without an automated process.

DependencyCheck, a critical tool for identifying vulnerabilities in project dependencies, was recently updated to version 9.0.0, introducing new complexities:

  • Rate limits
  • Breaking changes (new database schema)

All of those generated an impact into our CI/CD pipelines.

This article outlines a solution to these challenges using Docker-Dependency-Check custom images.

The Challenge

The enforcement of rate limits on the NVD API led to significant disruptions in projects.

The deprecation of NVD data feeds on December 15th, 2023, combined with the enforcement of rate limits on the NVD API, led to significant disruptions in projects.

Generated with DALL-E

Managing a single NVD API key across numerous projects often led to frequent rate limit exceedances, resulting in build failures or significantly increased execution times during updates. These delays compounded the time already spent on downloading and uploading the NVD data cache in pipeline jobs.

Additionally, breaking changes in Dependency-Check 9.0.0 required database schema updates, introducing further complexities. This necessitated purging existing NVD data or recreating caches while ensuring consistent DependencyCheck versions across all active branches.

Our Approach

We streamlined the update process by creating a centralized docker-dependency-check repository to mitigate rate limit issues.

Recognizing the need to isolate and centralize the update process, the next step was to define an implementation strategy that fulfills this goal and enables projects to benefit from it, breaking the process into:

  • Docker Image Creation
  • Managing Schema Changes
  • Dynamic configurations

Centralized Dependency Management

To tackle these issues, we created a centralized docker-dependency-check repository.

Generated with DALL-E

This project generates Docker images for Gradle, Maven, and CLI tools, updating the NVD data every four hours via scheduled pipeline.

By centralizing the update process, only one project interacts with the NVD API, mitigating rate limit issues.

Moreover, job performance in projects using these images has significantly improved. These jobs are now free from performing updates and maintaining an NVD data cache. This saves the time previously spent on cache download and upload and prevents the waste of resources caused by maintaining multiple caches with similar NVD data across all projects.

Implementation Strategy

By building new images with a fresh database for each DependencyCheck version, we ensured compatibility and mitigated risk of breaking changes.

Docker Image Creation

We create images with updated NVD data for the tools used across projects:

  • CLI: Based on owasp/dependency-check
  • Maven: Based on lightweight maven alpine image
  • Gradle: Based on on lightweight gradle alpine image
Generated with DALL-E

Managing Schema Changes

To handle breaking changes, as happened in Dependency-Check 9.0.0, we configured our setup to build new images with a fresh database for every new DependencyCheck version, ensuring compatibility between the database schema and the new DependencyCheck version.

Meanwhile, when working with the same DependencyCheck version, we reuse our most recently generated images solely to perform updates on them to acquire data incorporated to NVD within the timeframe our scheduled pipeline runs, adjusted to 4 hours, matching default value for validity of data before checking for updates, thus preventing projects using our images from checking for updates.

Managing Schema Changes flowchart

By doing so, we ensure our latest images use the most recent version of DependencyCheck and an up-to-date database.

Generated with DALL-E

Dynamic configurations

While the CLI setup is straightforward due to the absence of external configurations, Maven and Gradle images presented additional challenges since their configuration files are embedded within the projects.

To prevent breaking changes between the provided data and the DependencyCheck version, we introduced the environment variables OWASP_VERSION and OWASP_DATA_DIRECTORY in the Maven and Gradle images to manage versioning and data paths, supporting both CI and local development environments by applying following configurations in projects:

Maven (pom.xml)

Create properties with fall back value in the event environment variables used in context are not available

<properties>
<owasp.version>9.0.9</owasp.version>
<!-- If empty it will be default -->
<owasp.dataDirectory></owasp.dataDirectory>
</properties>

Create profiles to override each property in the event relevant environment variable exists in context

<profiles>
<profile>
<id>owasp-version-from-env</id>
<activation>
<property>
<name>env.OWASP_VERSION</name>
</property>
</activation>
<properties>
<!-- This overrides the default if the environment variable OWASP_VERSION is set -->
<owasp.version>${env.OWASP_VERSION}</owasp.version>
</properties>
</profile>
<profile>
<id>owasp-directory-from-env</id>
<activation>
<property>
<name>env.OWASP_DATA_DIRECTORY</name>
</property>
</activation>
<properties>
<!-- This overrides the default if the environment variable OWASP_DATA_DIRECTORY is set. It specifies path for SQL CVEs contents, using env variable from custom image with most recent updates ($REGISTRY/docker-dependency-check-mvn:latest) to be used in CI env -->
<owasp.dataDirectory>${env.OWASP_DATA_DIRECTORY}</owasp.dataDirectory>
</properties>
</profile>
</profiles>

Gradle groovy (build.gradle)

Use the OWASP_VERSION environment variable provided within the image for your owasp dependency check plugin version, specifying also a fall back version for contexts where environment variable is not defined in your build.gradle:

buildscript {
// Fetch the OWASP_VERSION environment variable
String owaspVersion = System.getenv('OWASP_VERSION') ?: '9.0.9'

repositories {
mavenCentral()
}
dependencies {
classpath "org.owasp:dependency-check-gradle:$owaspVersion"
}
}
apply plugin: 'org.owasp.dependencycheck'

Add the following logic to use property data.directory as shown below to you OWASP Dependency Check Configuration in build.gradle referencing image provided environment variable OWASP_DATA_DIRECTORY otherwise using default path.

dependencyCheck {
// Specify path for SQL CVEs contents, using env variable from custom image with most recent updates ($REGISTRY/dependency-check-gradle:latest) to be used in CI env, otherwise default path
if (System.getenv('OWASP_DATA_DIRECTORY')) {
data.directory = System.getenv('OWASP_DATA_DIRECTORY')
}
}

Gradle kotlin (build.gradle.kts)

Use the OWASP_VERSION environment variable provided within the image for your owasp dependency check plugin version, specifying also a fall back version for contexts where environment variable is not defined:

buildscript {
val owaspVersion: String = System.getenv("OWASP_VERSION") ?: "9.0.9"

repositories {
mavenCentral()
}
dependencies {
classpath("org.owasp:dependency-check-gradle:$owaspVersion")
}
}

apply(plugin = "org.owasp.dependencycheck")

Add the following logic to use property data.directory as shown below to you OWASP Dependency Check Configuration in build.gradle.kts referencing image provided environment variable OWASP_DATA_DIRECTORY otherwise default path.

configure<org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension> {
// Specify path for SQL CVEs contents, using env variable from custom image with most recent updates ($REGISTRY/docker-dependency-check-gradle:latest) to be used in CI env, otherwise default path
System.getenv("OWASP_DATA_DIRECTORY")?.let {
data.directory = it
}
}
Dynamic configurations flowchart

CI job configuration samples:

For CLI:

owasp_dependency_check:
image:
name: $REGISTRY/docker-dependency-check-cli:latest
stage: code-analysis
script:
- >
/usr/share/dependency-check/bin/dependency-check.sh --scan "./"
--project "$CI_PROJECT_NAME"

For Maven:

owasp-dependency-check:
image:
name: $REGISTRY/docker-dependency-check-mvn:latest
stage: code-analysis
script:
- mvn org.owasp:dependency-check-maven:check

For Gradle:

owasp-dependency-check:
image:
name: $REGISTRY/docker-dependency-check-gradle:latest
stage: code-analysis
script:
- gradle dependencyCheckAnalyze

Impact and Results

Centralized updates and streamlined resource usage have significantly improved job performance and stability across projects.

  • Job Performance: Centralized updates have minimized rate limit exceedances, reducing build failures and update times.
  • Resource Efficiency: Eliminated redundant caches, optimizing storage and resource use.
  • Consistency: Ensured uniform DependencyCheck versions across projects, enhancing CI/CD pipelines stability.
  • Stability: Mitigated the risk of breaking changes, providing a more stable and predictable environment for ongoing development.

Conclusion

By centralizing the NVD updates and leveraging Docker images, we have streamlined our vulnerability management process.

This approach ensures high reliability across multiple projects and pipelines, ensuring timely detection and resolution of new vulnerabilities while mitigating rate limit issues and keeping our analyses current with the latest NVD updates.

--

--