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

clean: Unencode BOM ref and PURL for components in SBOM files [TAROT-3083] #118

Merged
merged 3 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 to helps downstream consumers of the SBOM file.
afsmeira marked this conversation as resolved.
Show resolved Hide resolved
//
// This function mutates the provided BOM.
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.``
afsmeira marked this conversation as resolved.
Show resolved Hide resolved
// 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