Skip to content

Commit

Permalink
Ensure SPDXIDs are valid (anchore#955)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored Apr 14, 2022
1 parent f53018f commit 0e77333
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 28 deletions.
13 changes: 13 additions & 0 deletions internal/formats/common/spdxhelpers/spdxid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package spdxhelpers

import (
"regexp"
)

var expr = regexp.MustCompile("[^a-zA-Z0-9.-]")

// SPDX spec says SPDXID must be:
// "SPDXRef-"[idstring] where [idstring] is a unique string containing letters, numbers, ., and/or -
func SanitizeElementID(id string) string {
return expr.ReplaceAllString(id, "-")
}
39 changes: 39 additions & 0 deletions internal/formats/common/spdxhelpers/spdxid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package spdxhelpers

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_SanitizeElementID(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "letters",
expected: "letters",
},
{
input: "ssl-client",
expected: "ssl-client",
},
{
input: "ssl_client",
expected: "ssl-client",
},
{
input: "go-module-sigs.k8s.io/structured-merge-diff/v3",
expected: "go-module-sigs.k8s.io-structured-merge-diff-v3",
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
actual := SanitizeElementID(test.input)

assert.Equal(t, test.expected, actual)
})
}
}
30 changes: 3 additions & 27 deletions internal/formats/spdx22json/model/element_id.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,13 @@
package model

import "github.com/anchore/syft/internal/formats/common/spdxhelpers"

// ElementID represents the identifier string portion of an SPDX element
// identifier. DocElementID should be used for any attributes which can
// contain identifiers defined in a different SPDX document.
// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion.
type ElementID string

func (e ElementID) String() string {
return "SPDXRef-" + string(e)
}

// DocElementID represents an SPDX element identifier that could be defined
// in a different SPDX document, and therefore could have a "DocumentRef-"
// portion, such as Relationship and Annotations.
// ElementID is used for attributes in which a "DocumentRef-" portion cannot
// appear, such as a Package or File definition (since it is necessarily
// being defined in the present document).
// DocumentRefID will be the empty string for elements defined in the
// present document.
// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
// 'SPDXRef-' portions.
type DocElementID struct {
DocumentRefID string
ElementRefID ElementID
}

// RenderDocElementID takes a DocElementID and returns the string equivalent,
// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
// reinserted.
func (d DocElementID) String() string {
prefix := ""
if d.DocumentRefID != "" {
prefix = "DocumentRef-" + d.DocumentRefID + ":"
}
return prefix + d.ElementRefID.String()
return "SPDXRef-" + spdxhelpers.SanitizeElementID(string(e))
}
29 changes: 29 additions & 0 deletions internal/formats/spdx22tagvalue/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"testing"

"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)

var updateSpdxTagValue = flag.Bool("update-spdx-tv", false, "update the *.golden files for spdx-tv encoders")
Expand All @@ -31,6 +34,32 @@ func TestSPDXTagValueImageEncoder(t *testing.T) {
)
}

func TestSPDXJSONSPDXIDs(t *testing.T) {
var pkgs []pkg.Package
for _, name := range []string{"some/slashes", "@at-sign", "under_scores"} {
p := pkg.Package{
Name: name,
}
p.SetID()
pkgs = append(pkgs, p)
}
testutils.AssertEncoderAgainstGoldenSnapshot(t,
Format(),
sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(pkgs...),
},
Relationships: nil,
Source: source.Metadata{
Scheme: source.DirectoryScheme,
},
Descriptor: sbom.Descriptor{},
},
true,
spdxTagValueRedactor,
)
}

func spdxTagValueRedactor(s []byte) []byte {
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
SPDXVersion: SPDX-2.2
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: .
DocumentNamespace: https://anchore.com/syft/dir/e69056a9-935e-4f00-b85f-9467f5d99a92
LicenseListVersion: 3.16
Creator: Organization: Anchore, Inc
Creator: Tool: syft-[not provided]
Created: 2022-04-13T16:38:03Z

##### Package: @at-sign

PackageName: @at-sign
SPDXID: SPDXRef-Package---at-sign-739e4f0d93fb8298
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageLicenseConcluded: NONE
PackageLicenseDeclared: NONE
PackageCopyrightText: NOASSERTION

##### Package: some/slashes

PackageName: some/slashes
SPDXID: SPDXRef-Package--some-slashes-26db06648b24bff9
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageLicenseConcluded: NONE
PackageLicenseDeclared: NONE
PackageCopyrightText: NOASSERTION

##### Package: under_scores

PackageName: under_scores
SPDXID: SPDXRef-Package--under-scores-250cbfefcdea318b
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageLicenseConcluded: NONE
PackageLicenseDeclared: NONE
PackageCopyrightText: NOASSERTION

2 changes: 1 addition & 1 deletion internal/formats/spdx22tagvalue/to_format_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2

for _, p := range catalog.Sorted() {
// name should be guaranteed to be unique, but semantically useful and stable
id := fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID())
id := spdxhelpers.SanitizeElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID()))

// If the Concluded License is not the same as the Declared License, a written explanation should be provided
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
Expand Down

0 comments on commit 0e77333

Please sign in to comment.