From 08dfb0a3a57d21b7b65478834b1cab2b7d4609dd Mon Sep 17 00:00:00 2001 From: nscuro Date: Mon, 20 May 2024 15:52:04 +0200 Subject: [PATCH] Handle breaking change in Trivy server API `Application#libraries` has been renamed to `Application#packages` in Trivy 0.51.2. The `Library` type no longer exists. It's not possible to tell the Trivy version based on its API. To work around this, we now send both the `packages` and `libraries` fields with redundant information. Fields that the API does not expect are silently ignored. Fixes #3737 Signed-off-by: nscuro --- .../parser/trivy/model/Application.java | 47 +++++++++++++++---- .../parser/trivy/model/Library.java | 45 ------------------ .../tasks/scanners/TrivyAnalysisTask.java | 3 +- .../TrivyAnalysisTaskIntegrationTest.java | 43 ++++++++++++----- .../tasks/scanners/TrivyAnalysisTaskTest.java | 15 ++++++ 5 files changed, 87 insertions(+), 66 deletions(-) delete mode 100644 src/main/java/org/dependencytrack/parser/trivy/model/Library.java diff --git a/src/main/java/org/dependencytrack/parser/trivy/model/Application.java b/src/main/java/org/dependencytrack/parser/trivy/model/Application.java index fdffec2a10..d56f34abe4 100644 --- a/src/main/java/org/dependencytrack/parser/trivy/model/Application.java +++ b/src/main/java/org/dependencytrack/parser/trivy/model/Application.java @@ -19,20 +19,51 @@ package org.dependencytrack.parser.trivy.model; import java.util.ArrayList; +import java.util.List; public class Application { + private String type; - private ArrayList libraries; + private List packages; + + /** + * NB: GSON doesn't support serialization of getters, it can only deal with fields. + * Need to have libraries as redundant field to packages, with Jackson we could just + * use a computed getter with {@link com.fasterxml.jackson.annotation.JsonGetter}. + * Migrate this to Jackson eventually. + * + * @see GitHub issue + * @deprecated Kept for compatibility with Trivy <= 0.51.1 + */ + @Deprecated(forRemoval = true) + private List libraries; - public Application(String type) { + public Application(final String type) { this.type = type; - this.libraries = new ArrayList(); + this.packages = new ArrayList<>(); + this.libraries = new ArrayList<>(); + } + + public String getType() { + return type; + } + + public void setType(String value) { + this.type = value; } - public String getType() { return type; } - public void setType(String value) { this.type = value; } + public List getPackages() { + return packages; + } + + public void setPackages(List value) { + this.packages = value; + this.libraries = value; + } + + public void addPackage(Package value) { + this.packages.add(value); + this.libraries.add(value); + } - public ArrayList getLibraries() { return libraries; } - public void setLibraries(ArrayList value) { this.libraries = value; } - public void addLibrary(Library value) { this.libraries.add(value); } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/trivy/model/Library.java b/src/main/java/org/dependencytrack/parser/trivy/model/Library.java deleted file mode 100644 index d8092e186e..0000000000 --- a/src/main/java/org/dependencytrack/parser/trivy/model/Library.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of Dependency-Track. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.dependencytrack.parser.trivy.model; - -public class Library { - private String name; - private String version; - private String[] licenses; - private OS layer; - - public Library(String name, String version) { - this.name = name; - this.version = version; - this.licenses = new String[] {}; - this.layer = new OS(); - } - - public String getName() { return name; } - public void setName(String value) { this.name = value; } - - public String getVersion() { return version; } - public void setVersion(String value) { this.version = value; } - - public String[] getLicenses() { return licenses; } - public void setLicenses(String[] value) { this.licenses = value; } - - public OS getLayer() { return layer; } - public void setLayer(OS value) { this.layer = value; } -} diff --git a/src/main/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTask.java b/src/main/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTask.java index 17bb2d21e8..0dbb155cc9 100644 --- a/src/main/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTask.java +++ b/src/main/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTask.java @@ -53,7 +53,6 @@ import org.dependencytrack.parser.trivy.model.Application; import org.dependencytrack.parser.trivy.model.BlobInfo; import org.dependencytrack.parser.trivy.model.DeleteRequest; -import org.dependencytrack.parser.trivy.model.Library; import org.dependencytrack.parser.trivy.model.OS; import org.dependencytrack.parser.trivy.model.Options; import org.dependencytrack.parser.trivy.model.Package; @@ -224,7 +223,7 @@ public void analyze(final List components) { map.put(key, component); LOGGER.debug("add library %s".formatted(component.toString())); - app.addLibrary(new Library(name, component.getVersion())); + app.addPackage(new Package(name, component.getVersion(), null, null, null, null, null)); } else { String srcName = null; String srcVersion = null; diff --git a/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskIntegrationTest.java b/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskIntegrationTest.java index 952e229fca..1c86489cdb 100644 --- a/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskIntegrationTest.java +++ b/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskIntegrationTest.java @@ -26,14 +26,17 @@ import org.dependencytrack.model.Component; import org.dependencytrack.model.Project; import org.dependencytrack.model.Vulnerability; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.testcontainers.containers.GenericContainer; import org.testcontainers.images.PullPolicy; import org.testcontainers.utility.DockerImageName; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -42,23 +45,38 @@ import static org.dependencytrack.model.ConfigPropertyConstants.SCANNER_TRIVY_ENABLED; import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage; +@RunWith(Parameterized.class) public class TrivyAnalysisTaskIntegrationTest extends PersistenceCapableTest { - private static GenericContainer trivyContainer; + @Parameterized.Parameters(name = "[{index}] trivyVersion={0}") + public static Collection testParameters() { + return Arrays.asList(new Object[][]{ + {"0.51.1"}, // Pre breaking change of Application#libraries -> Application#packages + {"0.51.2"}, // Post breaking change of Application#libraries -> Application#packages + {"latest"} + }); + } + + private final String trivyVersion; + private GenericContainer trivyContainer; + + public TrivyAnalysisTaskIntegrationTest(String trivyVersion) { + this.trivyVersion = trivyVersion; + } - @BeforeClass + @Before + @Override @SuppressWarnings("resource") - public static void setUpClass() { - trivyContainer = new GenericContainer<>(DockerImageName.parse("aquasec/trivy:latest")) + public void before() throws Exception { + super.before(); + + trivyContainer = new GenericContainer<>(DockerImageName.parse("aquasec/trivy:" + trivyVersion)) .withImagePullPolicy(PullPolicy.alwaysPull()) .withCommand("server --listen :8080 --token TrivyToken") .withExposedPorts(8080) .waitingFor(forLogMessage(".*Listening :8080.*", 1)); trivyContainer.start(); - } - @Before - public void setUp() throws Exception { qm.createConfigProperty( SCANNER_TRIVY_ENABLED.getGroupName(), SCANNER_TRIVY_ENABLED.getPropertyName(), @@ -82,11 +100,14 @@ public void setUp() throws Exception { ); } - @AfterClass - public static void tearDownClass() { + @After + @Override + public void after() { if (trivyContainer != null) { trivyContainer.stop(); } + + super.after(); } @Test diff --git a/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskTest.java b/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskTest.java index ee4a074504..403b665a08 100644 --- a/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/scanners/TrivyAnalysisTaskTest.java @@ -366,10 +366,25 @@ Those using Woodstox to parse XML data may be vulnerable to Denial of Service at "applications": [ { "type": "jar", + "packages": [ + { + "name": "com.fasterxml.woodstox:woodstox-core", + "version": "5.0.0", + "src_name": "com.fasterxml.woodstox:woodstox-core", + "src_version": "5.0.0", + "licenses": [], + "layer": { + "eosl": false, + "extended": false + } + } + ], "libraries": [ { "name": "com.fasterxml.woodstox:woodstox-core", "version": "5.0.0", + "src_name": "com.fasterxml.woodstox:woodstox-core", + "src_version": "5.0.0", "licenses": [], "layer": { "eosl": false,