Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use UUID for Packages IDs from pom.xml files. #7879

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion integration/client_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func TestClientServer(t *testing.T) {
golden: "testdata/busybox-with-lockfile.json.golden",
},
{
name: "scan pox.xml with repo command in client/server mode",
name: "scan pom.xml with repo command in client/server mode",
args: csArgs{
Command: "repo",
RemoteAddrOption: "--server",
Expand Down Expand Up @@ -302,6 +302,7 @@ func TestClientServer(t *testing.T) {
}

runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{
fakeUUID: "3ff14136-e09f-4df9-80ea-%012d",
override: overrideFuncs(overrideUID, tt.override),
})
})
Expand Down
16 changes: 8 additions & 8 deletions integration/testdata/pom-cyclonedx.json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000005",
"serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007",
"version": 1,
"metadata": {
"timestamp": "2021-08-25T12:20:30+00:00",
Expand All @@ -17,7 +17,7 @@
]
},
"component": {
"bom-ref": "3ff14136-e09f-4df9-80ea-000000000001",
"bom-ref": "3ff14136-e09f-4df9-80ea-000000000003",
"type": "application",
"name": "testdata/fixtures/repo/pom",
"properties": [
Expand All @@ -30,7 +30,7 @@
},
"components": [
{
"bom-ref": "3ff14136-e09f-4df9-80ea-000000000002",
"bom-ref": "3ff14136-e09f-4df9-80ea-000000000004",
"type": "application",
"name": "pom.xml",
"properties": [
Expand All @@ -54,7 +54,7 @@
"properties": [
{
"name": "aquasecurity:trivy:PkgID",
"value": "com.example:log4shell:1.0-SNAPSHOT"
"value": "3ff14136-e09f-4df9-80ea-000000000001"
},
{
"name": "aquasecurity:trivy:PkgType",
Expand All @@ -72,7 +72,7 @@
"properties": [
{
"name": "aquasecurity:trivy:PkgID",
"value": "com.fasterxml.jackson.core:jackson-databind:2.9.1"
"value": "3ff14136-e09f-4df9-80ea-000000000002"
},
{
"name": "aquasecurity:trivy:PkgType",
Expand All @@ -83,13 +83,13 @@
],
"dependencies": [
{
"ref": "3ff14136-e09f-4df9-80ea-000000000001",
"ref": "3ff14136-e09f-4df9-80ea-000000000003",
"dependsOn": [
"3ff14136-e09f-4df9-80ea-000000000002"
"3ff14136-e09f-4df9-80ea-000000000004"
]
},
{
"ref": "3ff14136-e09f-4df9-80ea-000000000002",
"ref": "3ff14136-e09f-4df9-80ea-000000000004",
"dependsOn": [
"pkg:maven/com.example/[email protected]"
]
Expand Down
8 changes: 4 additions & 4 deletions integration/testdata/pom.json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2020-9548",
"PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1",
"PkgID": "3ff14136-e09f-4df9-80ea-000000000002",
"PkgName": "com.fasterxml.jackson.core:jackson-databind",
"PkgIdentifier": {
"PURL": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"UID": "9c69fdeffb7ee6d4"
"UID": "fd7dbd1e62564ea0"
},
"InstalledVersion": "2.9.1",
"FixedVersion": "2.9.10.4",
Expand Down Expand Up @@ -89,11 +89,11 @@
},
{
"VulnerabilityID": "CVE-2021-20190",
"PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1",
"PkgID": "3ff14136-e09f-4df9-80ea-000000000002",
"PkgName": "com.fasterxml.jackson.core:jackson-databind",
"PkgIdentifier": {
"PURL": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"UID": "9c69fdeffb7ee6d4"
"UID": "fd7dbd1e62564ea0"
},
"InstalledVersion": "2.9.1",
"FixedVersion": "2.9.10.7",
Expand Down
2 changes: 2 additions & 0 deletions pkg/dependency/parser/java/pom/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/uuid"
"github.com/aquasecurity/trivy/pkg/version/doc"
)

Expand All @@ -25,6 +26,7 @@ var (
)

type artifact struct {
ID uuid.UUID // UUID is required to build correctly dep tree when multiple modules contain dependencies with same GAV
GroupID string
ArtifactID string
Version version
Expand Down
44 changes: 25 additions & 19 deletions pkg/dependency/parser/java/pom/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import (
"golang.org/x/net/html/charset"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/uuid"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)

Expand Down Expand Up @@ -118,11 +118,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
rootArt := root.artifact()
rootArt.Relationship = ftypes.RelationshipRoot

return p.parseRoot(rootArt, make(map[string]struct{}))
return p.parseRoot(rootArt, make(map[string]uuid.UUID))
}

// nolint: gocyclo
func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ftypes.Package, []ftypes.Dependency, error) {
func (p *Parser) parseRoot(root artifact, uniqModules map[string]uuid.UUID) ([]ftypes.Package, []ftypes.Dependency, error) {
// Prepare a queue for dependencies
queue := newArtifactQueue()

Expand All @@ -135,7 +135,7 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft
deps ftypes.Dependencies
rootDepManagement []pomDependency
uniqArtifacts = make(map[string]artifact)
uniqDeps = make(map[string][]string)
uniqDeps = make(map[uuid.UUID][]string)
)

// Iterate direct and transitive dependencies
Expand All @@ -148,7 +148,8 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft
if _, ok := uniqModules[art.String()]; ok {
continue
}
uniqModules[art.String()] = struct{}{}
art.ID = uuid.New()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want the output reports to be as reproducible as possible. I'm considering another approach, but I have not yet come up with...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean using something like a hash instead of UUID?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's one of the options. Regardless of the method, I want the same value to be output as much as possible when scanning the same object.

uniqModules[art.String()] = art.ID

modulePkgs, moduleDeps, err := p.parseRoot(art, uniqModules)
if err != nil {
Expand Down Expand Up @@ -213,27 +214,33 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft

// Offline mode may be missing some fields.
if !art.IsEmpty() {
// Modules already have uuid
if art.ID == uuid.Nil {
art.ID = uuid.New()
}
// Override the version
uniqArtifacts[art.Name()] = artifact{
newArt := artifact{
ID: art.ID,
Version: art.Version,
Licenses: result.artifact.Licenses,
Relationship: art.Relationship,
Locations: art.Locations,
}
uniqArtifacts[art.Name()] = newArt

// save only dependency names
// version will be determined later
dependsOn := lo.Map(result.dependencies, func(a artifact, _ int) string {
return a.Name()
})
uniqDeps[packageID(art.Name(), art.Version.String())] = dependsOn
uniqDeps[newArt.ID] = dependsOn
}
}

// Convert to []ftypes.Package and []ftypes.Dependency
for name, art := range uniqArtifacts {
pkg := ftypes.Package{
ID: packageID(name, art.Version.String()),
ID: art.ID.String(),
Name: name,
Version: art.Version.String(),
Licenses: art.Licenses,
Expand All @@ -243,15 +250,18 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft
pkgs = append(pkgs, pkg)

// Convert dependency names into dependency IDs
dependsOn := lo.FilterMap(uniqDeps[pkg.ID], func(dependOnName string, _ int) (string, bool) {
ver := depVersion(dependOnName, uniqArtifacts)
return packageID(dependOnName, ver), ver != ""
dependsOn := lo.FilterMap(uniqDeps[art.ID], func(dependOnName string, _ int) (string, bool) {
id := depID(dependOnName, uniqArtifacts)
return id, id != ""
})

// `mvn` shows modules separately from the root package and does not show module nesting.
// So we can add all modules as dependencies of root package.
if art.Relationship == ftypes.RelationshipRoot {
dependsOn = append(dependsOn, lo.Keys(uniqModules)...)
uuids := lo.Map(lo.Values(uniqModules), func(id uuid.UUID, _ int) string {
return id.String()
})
dependsOn = append(dependsOn, uuids...)
}

sort.Strings(dependsOn)
Expand All @@ -269,10 +279,10 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft
return pkgs, deps, nil
}

// depVersion finds dependency in uniqArtifacts and return its version
func depVersion(depName string, uniqArtifacts map[string]artifact) string {
// depID finds dependency in uniqArtifacts and return its ID
func depID(depName string, uniqArtifacts map[string]artifact) string {
if art, ok := uniqArtifacts[depName]; ok {
return art.Version.String()
return art.ID.String()
}
return ""
}
Expand Down Expand Up @@ -821,10 +831,6 @@ func parseMavenMetadata(r io.Reader) (*Metadata, error) {
return parsed, nil
}

func packageID(name, version string) string {
return dependency.ID(ftypes.Pom, name, version)
}

// cf. https://github.com/apache/maven/blob/259404701402230299fe05ee889ecdf1c9dae816/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java#L482-L486
func isSnapshot(ver string) bool {
return strings.HasSuffix(ver, "SNAPSHOT") || ver == "LATEST"
Expand Down
Loading
Loading