Skip to content

Commit

Permalink
clean: Unencode BOM ref and PURL for components in SBOM files [TAROT-…
Browse files Browse the repository at this point in the history
…3083]
  • Loading branch information
afsmeira committed Jan 8, 2025
1 parent 7c442f6 commit ca39acd
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 13 deletions.
35 changes: 33 additions & 2 deletions internal/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (
"path/filepath"
"strings"

cdx "github.com/CycloneDX/cyclonedx-go"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/fanal/secret"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
tresult "github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
tcdx "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
ptypes "github.com/aquasecurity/trivy/pkg/types"
codacy "github.com/codacy/codacy-engine-golang-seed/v6"
"github.com/codacy/codacy-trivy/internal"
Expand Down Expand Up @@ -219,12 +220,13 @@ func (t codacyTrivy) getVulnerabilities(ctx context.Context, report ptypes.Repor

// getSBOM produces a SBOM result from `report`.
func (t codacyTrivy) getSBOM(ctx context.Context, report ptypes.Report) (codacy.SBOM, error) {
marshaler := cyclonedx.NewMarshaler(internal.TrivyVersion())
marshaler := tcdx.NewMarshaler(internal.TrivyVersion())
bom, err := marshaler.MarshalReport(ctx, report)
if err != nil {
return codacy.SBOM{}, &ToolError{msg: "Failed to run Codacy Trivy", w: err}
}

unencodeComponents(bom)
return codacy.SBOM{BOM: *bom}, nil
}

Expand Down Expand Up @@ -400,6 +402,35 @@ func findLeastDisruptiveFixedVersion(vuln ptypes.DetectedVulnerability) string {
return vuln.FixedVersion
}

// unencodeComponentes uses URL unencoding on component PURLs and BOMRefs.
//
// This helps downstream consumers of the SBOM file.
func unencodeComponents(bom *cdx.BOM) {
components := *bom.Components
for i, component := range components {
if purl, err := url.PathUnescape(component.PackageURL); err == nil {
components[i].PackageURL = purl
}
if bomRef, err := url.PathUnescape(component.BOMRef); err == nil {
components[i].BOMRef = bomRef
}
}

dependencies := *bom.Dependencies
for i, dependency := range dependencies {
if ref, err := url.PathUnescape(dependency.Ref); err == nil {
dependencies[i].Ref = ref
}

dDependencies := *dependency.Dependencies
for j, dDependency := range dDependencies {
if d, err := url.PathUnescape(dDependency); err == nil {
dDependencies[j] = d
}
}
}
}

// Remove the pkg: prefix and url-decode the PURL for display purposes.
func purlPrettyPrint(purl packageurl.PackageURL) string {
purlStripPkg := strings.TrimPrefix(purl.ToString(), "pkg:")
Expand Down
88 changes: 77 additions & 11 deletions internal/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func TestRun(t *testing.T) {
ctx := context.Background()
ctrl := gomock.NewController(t)

package1Purl := packageurl.NewPackageURL("type", "namespace", "package-1", "version", nil, "")
package2Purl := packageurl.NewPackageURL("type", "namespace", "package-2", "version", nil, "")
package1Purl := packageurl.NewPackageURL("type", "@namespace", "package-1", "version+incompatible", nil, "")
package2Purl := packageurl.NewPackageURL("type", "@namespace", "package-2", "version+RC", nil, "")

// Create a temporary file with a secret
srcDir, err := os.MkdirTemp("", "")
Expand Down Expand Up @@ -111,15 +111,22 @@ func TestRun(t *testing.T) {
},
},
Identifier: ftypes.PkgIdentifier{
PURL: package1Purl,
BOMRef: package1Purl.String(),
PURL: package1Purl,
UID: package1Purl.String(),
},
Relationship: ftypes.RelationshipDirect,
},
{
Identifier: ftypes.PkgIdentifier{
PURL: package2Purl,
BOMRef: package2Purl.String(),
PURL: package2Purl,
UID: package2Purl.String(),
},
Relationship: ftypes.RelationshipDirect,
},
},
Class: ptypes.ClassLangPkg,
Vulnerabilities: []ptypes.DetectedVulnerability{
// Will generate an issue
{
Expand Down Expand Up @@ -214,13 +221,13 @@ func TestRun(t *testing.T) {
File: fileName,
Line: 1,
PatternID: ruleIDVulnerability,
Message: "Insecure dependency type/namespace/package-1@version (vuln id: vuln title) (update to vuln fixed)",
Message: "Insecure dependency type/@namespace/package-1@version+incompatible (vuln id: vuln title) (update to vuln fixed)",
},
{
File: fileName,
Line: 1,
PatternID: ruleIDVulnerability,
Message: "Insecure dependency type/namespace/package-1@version (vuln id no fixed version: vuln no fixed version) (no fix available)",
Message: "Insecure dependency type/@namespace/package-1@version+incompatible (vuln id no fixed version: vuln no fixed version) (no fix available)",
},
{
File: fileName,
Expand Down Expand Up @@ -259,6 +266,9 @@ func TestRun(t *testing.T) {
})
assert.ElementsMatch(t, expectedFileErrors, fileErrors)

expectedMetadataComponentBOMRef := "b804b498-f626-41c5-a47f-45e1471acf33"
expectedRootComponentBOMRef := "d16d6083-4370-442f-a6ab-c5146a215dbe"
expectedRooComponentName := "file-802713450"
expectedSBOM := codacy.SBOM{
BOM: cyclonedx.BOM{
XMLNS: "http://cyclonedx.org/schema/bom/1.6",
Expand All @@ -280,7 +290,7 @@ func TestRun(t *testing.T) {
},
},
Component: &cyclonedx.Component{
BOMRef: "b804b498-f626-41c5-a47f-45e1471acf33", // different every run
BOMRef: expectedMetadataComponentBOMRef,
Type: "application",
Properties: &[]cyclonedx.Property{
{
Expand All @@ -290,10 +300,56 @@ func TestRun(t *testing.T) {
},
},
},
Components: &[]cyclonedx.Component{},
Components: &[]cyclonedx.Component{
{
BOMRef: expectedRootComponentBOMRef,
Type: "application",
Name: "file-802713450",
Properties: &[]cyclonedx.Property{
{
Name: "aquasecurity:trivy:Class",
Value: "lang-pkgs",
},
{
Name: "aquasecurity:trivy:Type",
},
},
},
{
BOMRef: "pkg:type/@namespace/package-1@version+incompatible",
Type: "library",
Properties: &[]cyclonedx.Property{},
PackageURL: "pkg:type/@namespace/package-1@version+incompatible",
Version: "version+incompatible",
},
{
BOMRef: "pkg:type/@namespace/package-2@version+RC",
Type: "library",
Properties: &[]cyclonedx.Property{},
PackageURL: "pkg:type/@namespace/package-2@version+RC",
Version: "version+RC",
},
},
Dependencies: &[]cyclonedx.Dependency{
{
Ref: "b804b498-f626-41c5-a47f-45e1471acf33",
Ref: expectedMetadataComponentBOMRef,
Dependencies: &[]string{
expectedRootComponentBOMRef,
},
},
{
Ref: expectedRootComponentBOMRef,
Dependencies: &[]string{
"pkg:type/@namespace/package-1@version+incompatible",
"pkg:type/@namespace/package-2@version+RC",
},
},
{
Ref: "pkg:type/@namespace/package-1@version+incompatible",
Dependencies: &[]string{},
},
{
Ref: "pkg:type/@namespace/package-2@version+RC",
Dependencies: &[]string{},
},
},
Expand All @@ -308,6 +364,18 @@ func TestRun(t *testing.T) {
return false
}
})

// Set values that change on every run to known values.``
// This allows us to test the relationship between components.
sboms[0].(codacy.SBOM).Metadata.Component.BOMRef = expectedMetadataComponentBOMRef
cs := *sboms[0].(codacy.SBOM).Components
cs[0].BOMRef = expectedRootComponentBOMRef
cs[0].Name = expectedRooComponentName
ds := *sboms[0].(codacy.SBOM).Dependencies
ds[0].Ref = expectedMetadataComponentBOMRef
ds[0].Dependencies = &[]string{expectedRootComponentBOMRef}
ds[1].Ref = expectedRootComponentBOMRef

// Only one SBOM result is produced
assert.Len(t, sboms, 1)
assert.True(
Expand All @@ -319,8 +387,6 @@ func TestRun(t *testing.T) {
// Ignore fields that change each run
cmpopts.IgnoreFields(codacy.SBOM{}, "SerialNumber"),
cmpopts.IgnoreFields(cyclonedx.Metadata{}, "Timestamp"),
cmpopts.IgnoreFields(cyclonedx.Component{}, "BOMRef"),
cmpopts.IgnoreFields(cyclonedx.Dependency{}, "Ref"),
},
),
)
Expand Down

0 comments on commit ca39acd

Please sign in to comment.