DependencyTrack / dependency-track

Dependency-Track is an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain.
https://dependencytrack.org/
Apache License 2.0
2.69k stars 578 forks source link

Cloned project refers to original component UUIDs in dependency graph #4153

Closed jimklimov closed 1 month ago

jimklimov commented 1 month ago

Current Behavior

When cloning a project in DT (via REST API or via DT UI by "adding a version" of a project) selecting all data categories (components, analysis results, etc.), I found that the clone's replica of vulnerability alerts does reference the new component copies' UUIDs (as expected); however if I click the links to see that dependency in the graph, I get nothing (just the root of graph page).

I checked that if I open the cloned project's dependency graph and click a component, I am led to the original project's copy of the component (with a different UUID).

I believe (but did not yet thoroughly check) that this is because the DIRECT_DEPENDENCIES database table entries (assuming PROJECT and COMPONENTS tables at least) were copied "as is" with the embedded JSON chunks that include dependency metadata and DT UUIDs, so the dependency tree in the clone project is an exact replica of the old tree with its old UUIDs.

While a subsequent BOM upload does re-evaluate the dependency tree and mention correct UUIDs in the graph, the clone is somewhat incorrect and misleading immediately after its making.

Is there some parameter (or extra call) to re-evaluate its graph in place, so it would mention the actual components assigned to this new project version?

In Slack discussion, @nscuro suggested that

Indeed appears to be a gap in the cloning logic. It only copies values as-is, whereas it should replace UUIDs as you rightfully expect.

...so posting the issue as agreed.

Steps to Reproduce

  1. Clone project ("add new version" in UI) inheriting all data categories
  2. Click around in Dependency Graph to open a component
  3. Observe project version assigned to the component when its details show up - the original old project is seen, not the new one

Expected Behavior

DIRECT_DEPENDENCIES strings are rewritten during cloning to match the newly instantiated copies of the original components (as part of the newly-atomic cloning operation, I believe).

Dependency-Track Version

4.11.7

Dependency-Track Distribution

Container Image

Database Server

PostgreSQL

Database Server Version

No response

Browser

Mozilla Firefox

Checklist

nscuro commented 1 month ago

Project cloning happens here: https://github.com/DependencyTrack/dependency-track/blob/ab9d19dbce5b752401fc736b64232ab702f12301/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java#L503-L658

jimklimov commented 1 month ago

I suppose a Map can help to correlate the old ("source") and new component instances just after component cloning https://github.com/DependencyTrack/dependency-track/blob/ab9d19dbce5b752401fc736b64232ab702f12301/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java#L581

UPDATE: Oh, you actually have one as clonedComponents, although it is by getId() (long) - I think this is the database ID number, so need to QueryManager.getObjectByUuid(...).getUuid() for the UUID needed for the rewrites... not sure it is efficient in a loop and maybe a Map<Component, Component> is better (can Java use complex objects as keys? Groovy can...) or keep a separate Map for caching of source ID to source UUID otherwise:

Anyway, then when the cloning of all components is done (and was requested at all), we can address the DIRECT_DEPENDENCIES content rewrite. Some first clues about it:

I think the implementation is not up to me, I was told my Java looks like shell script (so I liked Groovy better :D) Got a few naive ideas about fixing those UUID strings, e.g. looping with regex to try replacing Xn by Yn in each directDependencies string of all newly cloned components and the project, whether it is there or not. Maybe something more efficient is possible, but I guess this would at least work :D

nscuro commented 1 month ago

Yeah, tracking component references in one or more Maps, and then updating directDependencies for all in one go is the way to do it I think.

We probably need multiple maps:

We can then iterate over the latter, lookup cloned UUIDs in the former, and replace them accordingly in each Component's directDependencies.

Some ORM tweaks might be necessary, otherwise this will be a lot of UPDATE statements being executed in the background.

github-actions[bot] commented 2 weeks ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.