mirror of
https://github.com/anchore/syft.git
synced 2026-03-30 13:43:25 +02:00
feat: SPDX 2.3 support (#1311)
This commit is contained in:
parent
0c4b99c1c2
commit
42cb0a47a4
@ -40,7 +40,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft"
|
||||||
"github.com/anchore/syft/syft/event"
|
"github.com/anchore/syft/syft/event"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
@ -49,7 +49,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
allowedAttestFormats = []sbom.FormatID{
|
allowedAttestFormats = []sbom.FormatID{
|
||||||
syftjson.ID,
|
syftjson.ID,
|
||||||
spdx22json.ID,
|
spdxjson.ID,
|
||||||
cyclonedxjson.ID,
|
cyclonedxjson.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam
|
|||||||
|
|
||||||
func formatPredicateType(format sbom.Format) string {
|
func formatPredicateType(format sbom.Format) string {
|
||||||
switch format.ID() {
|
switch format.ID() {
|
||||||
case spdx22json.ID:
|
case spdxjson.ID:
|
||||||
return in_toto.PredicateSPDX
|
return in_toto.PredicateSPDX
|
||||||
case cyclonedxjson.ID:
|
case cyclonedxjson.ID:
|
||||||
return in_toto.PredicateCycloneDX
|
return in_toto.PredicateCycloneDX
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import (
|
|||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/github"
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/formats/table"
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
"github.com/anchore/syft/syft/formats/text"
|
"github.com/anchore/syft/syft/formats/text"
|
||||||
@ -21,9 +21,9 @@ func FormatAliases(ids ...sbom.FormatID) (aliases []string) {
|
|||||||
aliases = append(aliases, "text")
|
aliases = append(aliases, "text")
|
||||||
case table.ID:
|
case table.ID:
|
||||||
aliases = append(aliases, "table")
|
aliases = append(aliases, "table")
|
||||||
case spdx22json.ID:
|
case spdxjson.ID:
|
||||||
aliases = append(aliases, "spdx-json")
|
aliases = append(aliases, "spdx-json")
|
||||||
case spdx22tagvalue.ID:
|
case spdxtagvalue.ID:
|
||||||
aliases = append(aliases, "spdx-tag-value")
|
aliases = append(aliases, "spdx-tag-value")
|
||||||
case cyclonedxxml.ID:
|
case cyclonedxxml.ID:
|
||||||
aliases = append(aliases, "cyclonedx-xml")
|
aliases = append(aliases, "cyclonedx-xml")
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -32,7 +32,7 @@ require (
|
|||||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||||
github.com/sergi/go-diff v1.2.0
|
github.com/sergi/go-diff v1.2.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spdx/tools-golang v0.2.0
|
github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342
|
||||||
github.com/spf13/afero v1.8.2
|
github.com/spf13/afero v1.8.2
|
||||||
github.com/spf13/cobra v1.6.0
|
github.com/spf13/cobra v1.6.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -1817,8 +1817,8 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
|
|||||||
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||||
github.com/spdx/tools-golang v0.2.0 h1:KBNcw7xvVycRWeCWZK/5xQJA+plymW1+rTCs8ekJDro=
|
github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342 h1:6uvaOTv4GeRqQV6O1/znbpziqhctMRLTy3OGeZrNMic=
|
||||||
github.com/spdx/tools-golang v0.2.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo=
|
github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import (
|
|||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/github"
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/formats/table"
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
"github.com/anchore/syft/syft/formats/template"
|
"github.com/anchore/syft/syft/formats/template"
|
||||||
@ -23,8 +23,8 @@ const (
|
|||||||
CycloneDxXMLFormatID = cyclonedxxml.ID
|
CycloneDxXMLFormatID = cyclonedxxml.ID
|
||||||
CycloneDxJSONFormatID = cyclonedxjson.ID
|
CycloneDxJSONFormatID = cyclonedxjson.ID
|
||||||
GitHubFormatID = github.ID
|
GitHubFormatID = github.ID
|
||||||
SPDXTagValueFormatID = spdx22tagvalue.ID
|
SPDXTagValueFormatID = spdxtagvalue.ID
|
||||||
SPDXJSONFormatID = spdx22json.ID
|
SPDXJSONFormatID = spdxjson.ID
|
||||||
TemplateFormatID = template.ID
|
TemplateFormatID = template.ID
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,12 @@ import (
|
|||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
inputImage = "image"
|
||||||
|
inputDirectory = "dir"
|
||||||
|
inputFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string) {
|
func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string) {
|
||||||
name := DocumentName(srcMetadata)
|
name := DocumentName(srcMetadata)
|
||||||
return name, DocumentNamespace(name, srcMetadata)
|
return name, DocumentNamespace(name, srcMetadata)
|
||||||
@ -20,11 +26,11 @@ func DocumentNamespace(name string, srcMetadata source.Metadata) string {
|
|||||||
input := "unknown-source-type"
|
input := "unknown-source-type"
|
||||||
switch srcMetadata.Scheme {
|
switch srcMetadata.Scheme {
|
||||||
case source.ImageScheme:
|
case source.ImageScheme:
|
||||||
input = "image"
|
input = inputImage
|
||||||
case source.DirectoryScheme:
|
case source.DirectoryScheme:
|
||||||
input = "dir"
|
input = inputDirectory
|
||||||
case source.FileScheme:
|
case source.FileScheme:
|
||||||
input = "file"
|
input = inputFile
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueID := uuid.Must(uuid.NewRandom())
|
uniqueID := uuid.Must(uuid.NewRandom())
|
||||||
|
|||||||
@ -4,7 +4,7 @@ type ReferenceCategory string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SecurityReferenceCategory ReferenceCategory = "SECURITY"
|
SecurityReferenceCategory ReferenceCategory = "SECURITY"
|
||||||
PackageManagerReferenceCategory ReferenceCategory = "PACKAGE_MANAGER"
|
PackageManagerReferenceCategory ReferenceCategory = "PACKAGE-MANAGER"
|
||||||
OtherReferenceCategory ReferenceCategory = "OTHER"
|
OtherReferenceCategory ReferenceCategory = "OTHER"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,11 @@ func Test_Originator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Originator(test.input))
|
typ, value := Originator(test.input)
|
||||||
|
if typ != "" {
|
||||||
|
value = typ + ": " + value
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.expected, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import (
|
|||||||
// Originator needs to conform to the SPDX spec here:
|
// Originator needs to conform to the SPDX spec here:
|
||||||
// https://spdx.github.io/spdx-spec/package-information/#76-package-originator-field
|
// https://spdx.github.io/spdx-spec/package-information/#76-package-originator-field
|
||||||
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
||||||
func Originator(p pkg.Package) string {
|
// return values are: <type>, <value>
|
||||||
if hasMetadata(p) {
|
func Originator(p pkg.Package) (string, string) {
|
||||||
|
typ := ""
|
||||||
author := ""
|
author := ""
|
||||||
|
if hasMetadata(p) {
|
||||||
switch metadata := p.Metadata.(type) {
|
switch metadata := p.Metadata.(type) {
|
||||||
case pkg.ApkMetadata:
|
case pkg.ApkMetadata:
|
||||||
author = metadata.Maintainer
|
author = metadata.Maintainer
|
||||||
@ -29,13 +31,14 @@ func Originator(p pkg.Package) string {
|
|||||||
author = metadata.Authors[0]
|
author = metadata.Authors[0]
|
||||||
}
|
}
|
||||||
case pkg.RpmMetadata:
|
case pkg.RpmMetadata:
|
||||||
return "Organization: " + metadata.Vendor
|
typ = "Organization"
|
||||||
|
author = metadata.Vendor
|
||||||
case pkg.DpkgMetadata:
|
case pkg.DpkgMetadata:
|
||||||
author = metadata.Maintainer
|
author = metadata.Maintainer
|
||||||
}
|
}
|
||||||
if author != "" {
|
if typ == "" && author != "" {
|
||||||
return "Person: " + author
|
typ = "Person"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return typ, author
|
||||||
}
|
}
|
||||||
|
|||||||
461
syft/formats/common/spdxhelpers/to_format_model.go
Normal file
461
syft/formats/common/spdxhelpers/to_format_model.go
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spdx/tools-golang/spdx/common"
|
||||||
|
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/formats/common/util"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
spdxVersion = "SPDX-2.3"
|
||||||
|
noAssertion = "NOASSERTION"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToFormatModel creates and populates a new SPDX document struct that follows the SPDX 2.3
|
||||||
|
// spec from the given SBOM model.
|
||||||
|
//
|
||||||
|
//nolint:funlen
|
||||||
|
func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||||
|
name, namespace := DocumentNameAndNamespace(s.Source)
|
||||||
|
|
||||||
|
return &spdx.Document{
|
||||||
|
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
SPDXVersion: spdxVersion,
|
||||||
|
|
||||||
|
// 6.2: Data License; should be "CC0-1.0"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
DataLicense: "CC0-1.0",
|
||||||
|
|
||||||
|
// 6.3: SPDX Identifier; should be "DOCUMENT" to represent mandatory identifier of SPDXRef-DOCUMENT
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
SPDXIdentifier: "DOCUMENT",
|
||||||
|
|
||||||
|
// 6.4: Document Name
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
DocumentName: name,
|
||||||
|
|
||||||
|
// 6.5: Document Namespace
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// Purpose: Provide an SPDX document specific namespace as a unique absolute Uniform Resource
|
||||||
|
// Identifier (URI) as specified in RFC-3986, with the exception of the ‘#’ delimiter. The SPDX
|
||||||
|
// Document URI cannot contain a URI "part" (e.g. the "#" character), since the ‘#’ is used in SPDX
|
||||||
|
// element URIs (packages, files, snippets, etc) to separate the document namespace from the
|
||||||
|
// element’s SPDX identifier. Additionally, a scheme (e.g. “https:”) is required.
|
||||||
|
|
||||||
|
// The URI must be unique for the SPDX document including the specific version of the SPDX document.
|
||||||
|
// If the SPDX document is updated, thereby creating a new version, a new URI for the updated
|
||||||
|
// document must be used. There can only be one URI for an SPDX document and only one SPDX document
|
||||||
|
// for a given URI.
|
||||||
|
|
||||||
|
// Note that the URI does not have to be accessible. It is only intended to provide a unique ID.
|
||||||
|
// In many cases, the URI will point to a web accessible document, but this should not be assumed
|
||||||
|
// to be the case.
|
||||||
|
|
||||||
|
DocumentNamespace: namespace,
|
||||||
|
|
||||||
|
// 6.6: External Document References
|
||||||
|
// Cardinality: optional, one or many
|
||||||
|
ExternalDocumentReferences: nil,
|
||||||
|
|
||||||
|
// 6.11: Document Comment
|
||||||
|
// Cardinality: optional, one
|
||||||
|
DocumentComment: "",
|
||||||
|
|
||||||
|
CreationInfo: &spdx.CreationInfo{
|
||||||
|
// 6.7: License List Version
|
||||||
|
// Cardinality: optional, one
|
||||||
|
LicenseListVersion: spdxlicense.Version,
|
||||||
|
|
||||||
|
// 6.8: Creators: may have multiple keys for Person, Organization
|
||||||
|
// and/or Tool
|
||||||
|
// Cardinality: mandatory, one or many
|
||||||
|
Creators: []common.Creator{
|
||||||
|
{
|
||||||
|
Creator: "Anchore, Inc",
|
||||||
|
CreatorType: "Organization",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Creator: internal.ApplicationName + "-" + s.Descriptor.Version,
|
||||||
|
CreatorType: "Tool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 6.9: Created: data format YYYY-MM-DDThh:mm:ssZ
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
Created: time.Now().UTC().Format(time.RFC3339),
|
||||||
|
|
||||||
|
// 6.10: Creator Comment
|
||||||
|
// Cardinality: optional, one
|
||||||
|
CreatorComment: "",
|
||||||
|
},
|
||||||
|
Packages: toPackages(s.Artifacts.PackageCatalog),
|
||||||
|
Files: toFiles(s),
|
||||||
|
Relationships: toRelationships(s.Relationships),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||||
|
id := ""
|
||||||
|
if p, ok := identifiable.(pkg.Package); ok {
|
||||||
|
id = SanitizeElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID()))
|
||||||
|
} else {
|
||||||
|
id = string(identifiable.ID())
|
||||||
|
}
|
||||||
|
// NOTE: the spdx libraries prepend SPDXRef-, so we don't do it here
|
||||||
|
return common.ElementID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||||
|
//
|
||||||
|
//nolint:funlen
|
||||||
|
func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||||
|
for _, p := range catalog.Sorted() {
|
||||||
|
// name should be guaranteed to be unique, but semantically useful and stable
|
||||||
|
id := toSPDXID(p)
|
||||||
|
|
||||||
|
// 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 7.16). With respect to NOASSERTION, a written explanation in
|
||||||
|
// the Comments on License field (section 7.16) is preferred.
|
||||||
|
license := License(p)
|
||||||
|
checksums, filesAnalyzed := toPackageChecksums(p)
|
||||||
|
|
||||||
|
results = append(results, &spdx.Package{
|
||||||
|
// NOT PART OF SPEC
|
||||||
|
// flag: does this "package" contain files that were in fact "unpackaged",
|
||||||
|
// e.g. included directly in the Document without being in a Package?
|
||||||
|
IsUnpackaged: false,
|
||||||
|
|
||||||
|
// 7.1: Package Name
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
PackageName: p.Name,
|
||||||
|
|
||||||
|
// 7.2: Package SPDX Identifier: "SPDXRef-[idstring]"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
PackageSPDXIdentifier: id,
|
||||||
|
|
||||||
|
// 7.3: Package Version
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageVersion: p.Version,
|
||||||
|
|
||||||
|
// 7.4: Package File Name
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageFileName: "",
|
||||||
|
|
||||||
|
// 7.5: Package Supplier: may have single result for either Person or Organization,
|
||||||
|
// or NOASSERTION
|
||||||
|
// Cardinality: optional, one
|
||||||
|
|
||||||
|
// 7.6: Package Originator: may have single result for either Person or Organization,
|
||||||
|
// or NOASSERTION
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageSupplier: nil,
|
||||||
|
|
||||||
|
PackageOriginator: toPackageOriginator(p),
|
||||||
|
|
||||||
|
// 7.7: Package Download Location
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// NONE if there is no download location whatsoever.
|
||||||
|
// NOASSERTION if:
|
||||||
|
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||||
|
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||||
|
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||||
|
PackageDownloadLocation: DownloadLocation(p),
|
||||||
|
|
||||||
|
// 7.8: FilesAnalyzed
|
||||||
|
// Cardinality: optional, one; default value is "true" if omitted
|
||||||
|
|
||||||
|
// Purpose: Indicates whether the file content of this package has been available for or subjected to
|
||||||
|
// analysis when creating the SPDX document. If false, indicates packages that represent metadata or
|
||||||
|
// URI references to a project, product, artifact, distribution or a component. If false, the package
|
||||||
|
// must not contain any files.
|
||||||
|
|
||||||
|
// Intent: A package can refer to a project, product, artifact, distribution or a component that is
|
||||||
|
// external to the SPDX document.
|
||||||
|
FilesAnalyzed: filesAnalyzed,
|
||||||
|
// NOT PART OF SPEC: did FilesAnalyzed tag appear?
|
||||||
|
IsFilesAnalyzedTagPresent: true,
|
||||||
|
|
||||||
|
// 7.9: Package Verification Code
|
||||||
|
// Cardinality: optional, one if filesAnalyzed is true / omitted;
|
||||||
|
// zero (must be omitted) if filesAnalyzed is false
|
||||||
|
PackageVerificationCode: nil,
|
||||||
|
|
||||||
|
// 7.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5
|
||||||
|
// Cardinality: optional, one or many
|
||||||
|
|
||||||
|
// 7.10.1 Purpose: Provide an independently reproducible mechanism that permits unique identification of
|
||||||
|
// a specific package that correlates to the data in this SPDX file. This identifier enables a recipient
|
||||||
|
// to determine if any file in the original package has been changed. If the SPDX file is to be included
|
||||||
|
// in a package, this value should not be calculated. The SHA-1 algorithm will be used to provide the
|
||||||
|
// checksum by default.
|
||||||
|
PackageChecksums: checksums,
|
||||||
|
|
||||||
|
// 7.11: Package Home Page
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageHomePage: Homepage(p),
|
||||||
|
|
||||||
|
// 7.12: Source Information
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageSourceInfo: SourceInfo(p),
|
||||||
|
|
||||||
|
// 7.13: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// Purpose: Contain the license the SPDX file creator has concluded as governing the
|
||||||
|
// package or alternative values, if the governing license cannot be determined.
|
||||||
|
PackageLicenseConcluded: license,
|
||||||
|
|
||||||
|
// 7.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||||
|
// Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
|
||||||
|
// zero (must be omitted) if filesAnalyzed is false
|
||||||
|
PackageLicenseInfoFromFiles: nil,
|
||||||
|
|
||||||
|
// 7.15: Declared License: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// Purpose: List the licenses that have been declared by the authors of the package.
|
||||||
|
// Any license information that does not originate from the package authors, e.g. license
|
||||||
|
// information from a third party repository, should not be included in this field.
|
||||||
|
PackageLicenseDeclared: license,
|
||||||
|
|
||||||
|
// 7.16: Comments on License
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageLicenseComments: "",
|
||||||
|
|
||||||
|
// 7.17: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION"
|
||||||
|
// Cardinality: mandatory, one
|
||||||
|
// Purpose: IdentifyFormat the copyright holders of the package, as well as any dates present. This will be a free form text field extracted from package information files. The options to populate this field are limited to:
|
||||||
|
//
|
||||||
|
// Any text related to a copyright notice, even if not complete;
|
||||||
|
// NONE if the package contains no copyright information whatsoever; or
|
||||||
|
// NOASSERTION, if
|
||||||
|
// (i) the SPDX document creator has made no attempt to determine this field; or
|
||||||
|
// (ii) the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||||
|
//
|
||||||
|
PackageCopyrightText: noAssertion,
|
||||||
|
|
||||||
|
// 7.18: Package Summary Description
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageSummary: "",
|
||||||
|
|
||||||
|
// 7.19: Package Detailed Description
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageDescription: Description(p),
|
||||||
|
|
||||||
|
// 7.20: Package Comment
|
||||||
|
// Cardinality: optional, one
|
||||||
|
PackageComment: "",
|
||||||
|
|
||||||
|
// 7.21: Package External Reference
|
||||||
|
// Cardinality: optional, one or many
|
||||||
|
PackageExternalReferences: formatSPDXExternalRefs(p),
|
||||||
|
|
||||||
|
// 7.22: Package External Reference Comment
|
||||||
|
// Cardinality: conditional (optional, one) for each External Reference
|
||||||
|
// contained within PackageExternalReference2_1 struct, if present
|
||||||
|
|
||||||
|
// 7.23: Package Attribution Text
|
||||||
|
// Cardinality: optional, one or many
|
||||||
|
PackageAttributionTexts: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPackageOriginator(p pkg.Package) *common.Originator {
|
||||||
|
kind, originator := Originator(p)
|
||||||
|
if kind == "" || originator == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &common.Originator{
|
||||||
|
Originator: originator,
|
||||||
|
OriginatorType: kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||||
|
filesAnalyzed := false
|
||||||
|
var checksums []common.Checksum
|
||||||
|
switch meta := p.Metadata.(type) {
|
||||||
|
// we generate digest for some Java packages
|
||||||
|
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
||||||
|
case pkg.JavaMetadata:
|
||||||
|
if len(meta.ArchiveDigests) > 0 {
|
||||||
|
filesAnalyzed = true
|
||||||
|
for _, digest := range meta.ArchiveDigests {
|
||||||
|
checksums = append(checksums, common.Checksum{
|
||||||
|
Algorithm: common.ChecksumAlgorithm(digest.Algorithm),
|
||||||
|
Value: digest.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case pkg.GolangBinMetadata:
|
||||||
|
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("invalid h1digest: %s: %v", meta.H1Digest, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
algo = strings.ToUpper(algo)
|
||||||
|
checksums = append(checksums, common.Checksum{
|
||||||
|
Algorithm: common.ChecksumAlgorithm(algo),
|
||||||
|
Value: hexStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return checksums, filesAnalyzed
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference) {
|
||||||
|
for _, ref := range ExternalRefs(p) {
|
||||||
|
refs = append(refs, &spdx.PackageExternalReference{
|
||||||
|
Category: string(ref.ReferenceCategory),
|
||||||
|
RefType: string(ref.ReferenceType),
|
||||||
|
Locator: ref.ReferenceLocator,
|
||||||
|
ExternalRefComment: ref.Comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return refs
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRelationships(relationships []artifact.Relationship) (result []*spdx.Relationship) {
|
||||||
|
for _, r := range relationships {
|
||||||
|
exists, relationshipType, comment := lookupRelationship(r.Type)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
log.Debugf("unable to convert relationship to SPDX, dropping: %+v", r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we are only currently including Package -> * relationships
|
||||||
|
if _, ok := r.From.(pkg.Package); !ok {
|
||||||
|
log.Debugf("skipping non-package relationship: %+v", r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, &spdx.Relationship{
|
||||||
|
RefA: common.DocElementID{
|
||||||
|
ElementRefID: toSPDXID(r.From),
|
||||||
|
},
|
||||||
|
Relationship: string(relationshipType),
|
||||||
|
RefB: common.DocElementID{
|
||||||
|
ElementRefID: toSPDXID(r.To),
|
||||||
|
},
|
||||||
|
RelationshipComment: comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupRelationship(ty artifact.RelationshipType) (bool, RelationshipType, string) {
|
||||||
|
switch ty {
|
||||||
|
case artifact.ContainsRelationship:
|
||||||
|
return true, ContainsRelationship, ""
|
||||||
|
case artifact.OwnershipByFileOverlapRelationship:
|
||||||
|
return true, OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
||||||
|
}
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFiles(s sbom.SBOM) (results []*spdx.File) {
|
||||||
|
artifacts := s.Artifacts
|
||||||
|
|
||||||
|
for _, coordinates := range s.AllCoordinates() {
|
||||||
|
var metadata *source.FileMetadata
|
||||||
|
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
|
||||||
|
metadata = &metadataForLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
var digests []file.Digest
|
||||||
|
if digestsForLocation, exists := artifacts.FileDigests[coordinates]; exists {
|
||||||
|
digests = digestsForLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add file classifications (?) and content as a snippet
|
||||||
|
|
||||||
|
var comment string
|
||||||
|
if coordinates.FileSystemID != "" {
|
||||||
|
comment = fmt.Sprintf("layerID: %s", coordinates.FileSystemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, &spdx.File{
|
||||||
|
FileSPDXIdentifier: toSPDXID(coordinates),
|
||||||
|
FileComment: comment,
|
||||||
|
// required, no attempt made to determine license information
|
||||||
|
LicenseConcluded: noAssertion,
|
||||||
|
Checksums: toFileChecksums(digests),
|
||||||
|
FileName: coordinates.RealPath,
|
||||||
|
FileTypes: toFileTypes(metadata),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by real path then virtual path to ensure the result is stable across multiple runs
|
||||||
|
sort.SliceStable(results, func(i, j int) bool {
|
||||||
|
if results[i].FileName == results[j].FileName {
|
||||||
|
return results[i].FileSPDXIdentifier < results[j].FileSPDXIdentifier
|
||||||
|
}
|
||||||
|
return results[i].FileName < results[j].FileName
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||||
|
for _, digest := range digests {
|
||||||
|
checksums = append(checksums, common.Checksum{
|
||||||
|
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
||||||
|
Value: digest.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return checksums
|
||||||
|
}
|
||||||
|
|
||||||
|
func toChecksumAlgorithm(algorithm string) common.ChecksumAlgorithm {
|
||||||
|
// this needs to be an uppercase version of our algorithm
|
||||||
|
return common.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||||
|
if metadata == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeTypePrefix := strings.Split(metadata.MIMEType, "/")[0]
|
||||||
|
switch mimeTypePrefix {
|
||||||
|
case "image":
|
||||||
|
ty = append(ty, string(ImageFileType))
|
||||||
|
case "video":
|
||||||
|
ty = append(ty, string(VideoFileType))
|
||||||
|
case "application":
|
||||||
|
ty = append(ty, string(ApplicationFileType))
|
||||||
|
case "text":
|
||||||
|
ty = append(ty, string(TextFileType))
|
||||||
|
case "audio":
|
||||||
|
ty = append(ty, string(AudioFileType))
|
||||||
|
}
|
||||||
|
|
||||||
|
if internal.IsExecutable(metadata.MIMEType) {
|
||||||
|
ty = append(ty, string(BinaryFileType))
|
||||||
|
}
|
||||||
|
|
||||||
|
if internal.IsArchive(metadata.MIMEType) {
|
||||||
|
ty = append(ty, string(ArchiveFileType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add support for source, spdx, and documentation file types
|
||||||
|
if len(ty) == 0 {
|
||||||
|
ty = append(ty, string(OtherFileType))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ty
|
||||||
|
}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
package spdx22json
|
package spdxhelpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spdx/tools-golang/spdx/common"
|
||||||
|
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json/model"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
@ -28,7 +28,7 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "application/vnd.unknown",
|
MIMEType: "application/vnd.unknown",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.ApplicationFileType),
|
string(ApplicationFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,8 +37,8 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "application/zip",
|
MIMEType: "application/zip",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.ApplicationFileType),
|
string(ApplicationFileType),
|
||||||
string(spdxhelpers.ArchiveFileType),
|
string(ArchiveFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -47,7 +47,7 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "audio/ogg",
|
MIMEType: "audio/ogg",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.AudioFileType),
|
string(AudioFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "video/3gpp",
|
MIMEType: "video/3gpp",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.VideoFileType),
|
string(VideoFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -65,7 +65,7 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "text/html",
|
MIMEType: "text/html",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.TextFileType),
|
string(TextFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -74,7 +74,7 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "image/png",
|
MIMEType: "image/png",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.ImageFileType),
|
string(ImageFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -83,8 +83,8 @@ func Test_toFileTypes(t *testing.T) {
|
|||||||
MIMEType: "application/x-sharedlib",
|
MIMEType: "application/x-sharedlib",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
string(spdxhelpers.ApplicationFileType),
|
string(ApplicationFileType),
|
||||||
string(spdxhelpers.BinaryFileType),
|
string(BinaryFileType),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -100,18 +100,18 @@ func Test_lookupRelationship(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
input artifact.RelationshipType
|
input artifact.RelationshipType
|
||||||
exists bool
|
exists bool
|
||||||
ty spdxhelpers.RelationshipType
|
ty RelationshipType
|
||||||
comment string
|
comment string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: artifact.ContainsRelationship,
|
input: artifact.ContainsRelationship,
|
||||||
exists: true,
|
exists: true,
|
||||||
ty: spdxhelpers.ContainsRelationship,
|
ty: ContainsRelationship,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: artifact.OwnershipByFileOverlapRelationship,
|
input: artifact.OwnershipByFileOverlapRelationship,
|
||||||
exists: true,
|
exists: true,
|
||||||
ty: spdxhelpers.OtherRelationship,
|
ty: OtherRelationship,
|
||||||
comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ func Test_toFileChecksums(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
digests []file.Digest
|
digests []file.Digest
|
||||||
expected []model.Checksum
|
expected []common.Checksum
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
@ -150,14 +150,14 @@ func Test_toFileChecksums(t *testing.T) {
|
|||||||
Value: "meh",
|
Value: "meh",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []model.Checksum{
|
expected: []common.Checksum{
|
||||||
{
|
{
|
||||||
Algorithm: "SHA256",
|
Algorithm: "SHA256",
|
||||||
ChecksumValue: "deadbeefcafe",
|
Value: "deadbeefcafe",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Algorithm: "MD5",
|
Algorithm: "MD5",
|
||||||
ChecksumValue: "meh",
|
Value: "meh",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -170,7 +170,6 @@ func Test_toFileChecksums(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_fileIDsForPackage(t *testing.T) {
|
func Test_fileIDsForPackage(t *testing.T) {
|
||||||
|
|
||||||
p := pkg.Package{
|
p := pkg.Package{
|
||||||
Name: "bogus",
|
Name: "bogus",
|
||||||
}
|
}
|
||||||
@ -180,15 +179,19 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
FileSystemID: "nowhere",
|
FileSystemID: "nowhere",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
docElementId := func(identifiable artifact.Identifiable) common.DocElementID {
|
||||||
|
return common.DocElementID{
|
||||||
|
ElementRefID: toSPDXID(identifiable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
id string
|
|
||||||
relationships []artifact.Relationship
|
relationships []artifact.Relationship
|
||||||
expected []string
|
expected []*spdx.Relationship
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "find file IDs for packages with package-file relationships",
|
name: "package-to-file contains relationships",
|
||||||
id: model.ElementID(p.ID()).String(),
|
|
||||||
relationships: []artifact.Relationship{
|
relationships: []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: p,
|
From: p,
|
||||||
@ -196,13 +199,16 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
Type: artifact.ContainsRelationship,
|
Type: artifact.ContainsRelationship,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []*spdx.Relationship{
|
||||||
model.ElementID(c.ID()).String(),
|
{
|
||||||
|
Relationship: "CONTAINS",
|
||||||
|
RefA: docElementId(p),
|
||||||
|
RefB: docElementId(c),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ignore package-to-package",
|
name: "package-to-package",
|
||||||
id: model.ElementID(p.ID()).String(),
|
|
||||||
relationships: []artifact.Relationship{
|
relationships: []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: p,
|
From: p,
|
||||||
@ -210,11 +216,16 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
Type: artifact.ContainsRelationship,
|
Type: artifact.ContainsRelationship,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []string{},
|
expected: []*spdx.Relationship{
|
||||||
|
{
|
||||||
|
Relationship: "CONTAINS",
|
||||||
|
RefA: docElementId(p),
|
||||||
|
RefB: docElementId(p),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ignore file-to-file",
|
name: "ignore file-to-file",
|
||||||
id: model.ElementID(p.ID()).String(),
|
|
||||||
relationships: []artifact.Relationship{
|
relationships: []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: c,
|
From: c,
|
||||||
@ -222,11 +233,10 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
Type: artifact.ContainsRelationship,
|
Type: artifact.ContainsRelationship,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []string{},
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ignore file-to-package",
|
name: "ignore file-to-package",
|
||||||
id: model.ElementID(p.ID()).String(),
|
|
||||||
relationships: []artifact.Relationship{
|
relationships: []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: c,
|
From: c,
|
||||||
@ -234,11 +244,10 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
Type: artifact.ContainsRelationship,
|
Type: artifact.ContainsRelationship,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []string{},
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "filter by relationship type",
|
name: "include package-to-file overlap relationships",
|
||||||
id: model.ElementID(p.ID()).String(),
|
|
||||||
relationships: []artifact.Relationship{
|
relationships: []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: p,
|
From: p,
|
||||||
@ -246,12 +255,20 @@ func Test_fileIDsForPackage(t *testing.T) {
|
|||||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []string{},
|
expected: []*spdx.Relationship{
|
||||||
|
{
|
||||||
|
Relationship: "OTHER",
|
||||||
|
RefA: docElementId(p),
|
||||||
|
RefB: docElementId(c),
|
||||||
|
RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.ElementsMatch(t, test.expected, fileIDsForPackage(test.id, test.relationships))
|
relationships := toRelationships(test.relationships)
|
||||||
|
assert.Equal(t, test.expected, relationships)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,15 +320,17 @@ func Test_H1Digest(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
catalog := pkg.NewCatalog(test.pkg)
|
catalog := pkg.NewCatalog(test.pkg)
|
||||||
pkgs := toPackages(catalog, nil)
|
pkgs := toPackages(catalog)
|
||||||
require.Len(t, pkgs, 1)
|
require.Len(t, pkgs, 1)
|
||||||
p := pkgs[0]
|
for _, p := range pkgs {
|
||||||
if test.expectedDigest == "" {
|
if test.expectedDigest == "" {
|
||||||
require.Len(t, p.Checksums, 0)
|
require.Len(t, p.PackageChecksums, 0)
|
||||||
} else {
|
} else {
|
||||||
require.Len(t, p.Checksums, 1)
|
require.Len(t, p.PackageChecksums, 1)
|
||||||
c := p.Checksums[0]
|
for _, c := range p.PackageChecksums {
|
||||||
require.Equal(t, test.expectedDigest, fmt.Sprintf("%s:%s", c.Algorithm, c.ChecksumValue))
|
require.Equal(t, test.expectedDigest, fmt.Sprintf("%s:%s", c.Algorithm, c.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/spdx"
|
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
@ -19,7 +19,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
|
func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
|
||||||
if doc == nil {
|
if doc == nil {
|
||||||
return nil, errors.New("cannot convert SPDX document to Syft model because document is nil")
|
return nil, errors.New("cannot convert SPDX document to Syft model because document is nil")
|
||||||
}
|
}
|
||||||
@ -27,9 +27,7 @@ func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
|
|||||||
spdxIDMap := make(map[string]interface{})
|
spdxIDMap := make(map[string]interface{})
|
||||||
|
|
||||||
src := source.Metadata{Scheme: source.UnknownScheme}
|
src := source.Metadata{Scheme: source.UnknownScheme}
|
||||||
if doc.CreationInfo != nil {
|
src.Scheme = extractSchemeFromNamespace(doc.DocumentNamespace)
|
||||||
src.Scheme = extractSchemeFromNamespace(doc.CreationInfo.DocumentNamespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &sbom.SBOM{
|
s := &sbom.SBOM{
|
||||||
Source: src,
|
Source: src,
|
||||||
@ -63,18 +61,18 @@ func extractSchemeFromNamespace(ns string) source.Scheme {
|
|||||||
parts := strings.Split(u.Path, "/")
|
parts := strings.Split(u.Path, "/")
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
switch p {
|
switch p {
|
||||||
case "file":
|
case inputFile:
|
||||||
return source.FileScheme
|
return source.FileScheme
|
||||||
case "image":
|
case inputImage:
|
||||||
return source.ImageScheme
|
return source.ImageScheme
|
||||||
case "dir":
|
case inputDirectory:
|
||||||
return source.DirectoryScheme
|
return source.DirectoryScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return source.UnknownScheme
|
return source.UnknownScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release {
|
func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
|
||||||
for _, p := range doc.Packages {
|
for _, p := range doc.Packages {
|
||||||
purlValue := findPURLValue(p)
|
purlValue := findPURLValue(p)
|
||||||
if purlValue == "" {
|
if purlValue == "" {
|
||||||
@ -107,7 +105,7 @@ func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document2_2) {
|
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document) {
|
||||||
for _, p := range doc.Packages {
|
for _, p := range doc.Packages {
|
||||||
syftPkg := toSyftPackage(p)
|
syftPkg := toSyftPackage(p)
|
||||||
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
||||||
@ -115,8 +113,8 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *sp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document2_2) {
|
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document) {
|
||||||
for _, f := range doc.UnpackagedFiles {
|
for _, f := range doc.Files {
|
||||||
l := toSyftLocation(f)
|
l := toSyftLocation(f)
|
||||||
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
||||||
|
|
||||||
@ -125,8 +123,8 @@ func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toFileDigests(f *spdx.File2_2) (digests []file.Digest) {
|
func toFileDigests(f *spdx.File) (digests []file.Digest) {
|
||||||
for _, digest := range f.FileChecksums {
|
for _, digest := range f.Checksums {
|
||||||
digests = append(digests, file.Digest{
|
digests = append(digests, file.Digest{
|
||||||
Algorithm: string(digest.Algorithm),
|
Algorithm: string(digest.Algorithm),
|
||||||
Value: digest.Value,
|
Value: digest.Value,
|
||||||
@ -135,9 +133,9 @@ func toFileDigests(f *spdx.File2_2) (digests []file.Digest) {
|
|||||||
return digests
|
return digests
|
||||||
}
|
}
|
||||||
|
|
||||||
func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) {
|
func toFileMetadata(f *spdx.File) (meta source.FileMetadata) {
|
||||||
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
||||||
for _, typ := range f.FileType {
|
for _, typ := range f.FileTypes {
|
||||||
switch FileType(typ) {
|
switch FileType(typ) {
|
||||||
case ImageFileType:
|
case ImageFileType:
|
||||||
meta.MIMEType = "image/"
|
meta.MIMEType = "image/"
|
||||||
@ -157,11 +155,11 @@ func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) {
|
|||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2) []artifact.Relationship {
|
func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) []artifact.Relationship {
|
||||||
var out []artifact.Relationship
|
var out []artifact.Relationship
|
||||||
for _, r := range doc.Relationships {
|
for _, r := range doc.Relationships {
|
||||||
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
||||||
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.CreationInfo.SPDXIdentifier) {
|
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) {
|
||||||
log.Debugf("ignoring relationship to external document: %+v", r)
|
log.Debugf("ignoring relationship to external document: %+v", r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -205,7 +203,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
|
func toSyftCoordinates(f *spdx.File) source.Coordinates {
|
||||||
const layerIDPrefix = "layerID: "
|
const layerIDPrefix = "layerID: "
|
||||||
var fileSystemID string
|
var fileSystemID string
|
||||||
if strings.Index(f.FileComment, layerIDPrefix) == 0 {
|
if strings.Index(f.FileComment, layerIDPrefix) == 0 {
|
||||||
@ -220,7 +218,7 @@ func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftLocation(f *spdx.File2_2) *source.Location {
|
func toSyftLocation(f *spdx.File) *source.Location {
|
||||||
return &source.Location{
|
return &source.Location{
|
||||||
Coordinates: toSyftCoordinates(f),
|
Coordinates: toSyftCoordinates(f),
|
||||||
VirtualPath: f.FileName,
|
VirtualPath: f.FileName,
|
||||||
@ -255,7 +253,7 @@ func findQualifierValue(purl packageurl.PackageURL, qualifier string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractPkgInfo(p *spdx.Package2_2) pkgInfo {
|
func extractPkgInfo(p *spdx.Package) pkgInfo {
|
||||||
pu := findPURLValue(p)
|
pu := findPURLValue(p)
|
||||||
purl, err := packageurl.FromString(pu)
|
purl, err := packageurl.FromString(pu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -268,7 +266,7 @@ func extractPkgInfo(p *spdx.Package2_2) pkgInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftPackage(p *spdx.Package2_2) *pkg.Package {
|
func toSyftPackage(p *spdx.Package) *pkg.Package {
|
||||||
info := extractPkgInfo(p)
|
info := extractPkgInfo(p)
|
||||||
metadataType, metadata := extractMetadata(p, info)
|
metadataType, metadata := extractMetadata(p, info)
|
||||||
sP := pkg.Package{
|
sP := pkg.Package{
|
||||||
@ -289,7 +287,7 @@ func toSyftPackage(p *spdx.Package2_2) *pkg.Package {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interface{}) {
|
func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface{}) {
|
||||||
arch := info.qualifierValue(pkg.PURLQualifierArch)
|
arch := info.qualifierValue(pkg.PURLQualifierArch)
|
||||||
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
|
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
|
||||||
upstream := strings.SplitN(upstreamValue, "@", 2)
|
upstream := strings.SplitN(upstreamValue, "@", 2)
|
||||||
@ -298,12 +296,20 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
|
|||||||
if len(upstream) > 1 {
|
if len(upstream) > 1 {
|
||||||
upstreamVersion = upstream[1]
|
upstreamVersion = upstream[1]
|
||||||
}
|
}
|
||||||
|
supplier := ""
|
||||||
|
if p.PackageSupplier != nil {
|
||||||
|
supplier = p.PackageSupplier.Supplier
|
||||||
|
}
|
||||||
|
originator := ""
|
||||||
|
if p.PackageOriginator != nil {
|
||||||
|
originator = p.PackageOriginator.Originator
|
||||||
|
}
|
||||||
switch info.typ {
|
switch info.typ {
|
||||||
case pkg.ApkPkg:
|
case pkg.ApkPkg:
|
||||||
return pkg.ApkMetadataType, pkg.ApkMetadata{
|
return pkg.ApkMetadataType, pkg.ApkMetadata{
|
||||||
Package: p.PackageName,
|
Package: p.PackageName,
|
||||||
OriginPackage: upstreamName,
|
OriginPackage: upstreamName,
|
||||||
Maintainer: p.PackageSupplierPerson,
|
Maintainer: supplier,
|
||||||
Version: p.PackageVersion,
|
Version: p.PackageVersion,
|
||||||
License: p.PackageLicenseDeclared,
|
License: p.PackageLicenseDeclared,
|
||||||
Architecture: arch,
|
Architecture: arch,
|
||||||
@ -329,7 +335,7 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
|
|||||||
Arch: arch,
|
Arch: arch,
|
||||||
SourceRpm: upstreamValue,
|
SourceRpm: upstreamValue,
|
||||||
License: license,
|
License: license,
|
||||||
Vendor: p.PackageOriginatorOrganization,
|
Vendor: originator,
|
||||||
}
|
}
|
||||||
case pkg.DebPkg:
|
case pkg.DebPkg:
|
||||||
return pkg.DpkgMetadataType, pkg.DpkgMetadata{
|
return pkg.DpkgMetadataType, pkg.DpkgMetadata{
|
||||||
@ -338,12 +344,12 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
|
|||||||
Version: p.PackageVersion,
|
Version: p.PackageVersion,
|
||||||
SourceVersion: upstreamVersion,
|
SourceVersion: upstreamVersion,
|
||||||
Architecture: arch,
|
Architecture: arch,
|
||||||
Maintainer: p.PackageOriginatorPerson,
|
Maintainer: originator,
|
||||||
}
|
}
|
||||||
case pkg.JavaPkg:
|
case pkg.JavaPkg:
|
||||||
var digests []file.Digest
|
var digests []file.Digest
|
||||||
for algorithm, value := range p.PackageChecksums {
|
for _, value := range p.PackageChecksums {
|
||||||
digests = append(digests, file.Digest{Algorithm: string(algorithm), Value: value.Value})
|
digests = append(digests, file.Digest{Algorithm: string(value.Algorithm), Value: value.Value})
|
||||||
}
|
}
|
||||||
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
||||||
ArchiveDigests: digests,
|
ArchiveDigests: digests,
|
||||||
@ -366,7 +372,7 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
|
|||||||
return pkg.UnknownMetadataType, nil
|
return pkg.UnknownMetadataType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findPURLValue(p *spdx.Package2_2) string {
|
func findPURLValue(p *spdx.Package) string {
|
||||||
for _, r := range p.PackageExternalReferences {
|
for _, r := range p.PackageExternalReferences {
|
||||||
if r.RefType == string(PurlExternalRefType) {
|
if r.RefType == string(PurlExternalRefType) {
|
||||||
return r.Locator
|
return r.Locator
|
||||||
@ -375,7 +381,7 @@ func findPURLValue(p *spdx.Package2_2) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) {
|
func extractCPEs(p *spdx.Package) (cpes []pkg.CPE) {
|
||||||
for _, r := range p.PackageExternalReferences {
|
for _, r := range p.PackageExternalReferences {
|
||||||
if r.RefType == string(Cpe23ExternalRefType) {
|
if r.RefType == string(Cpe23ExternalRefType) {
|
||||||
cpe, err := pkg.NewCPE(r.Locator)
|
cpe, err := pkg.NewCPE(r.Locator)
|
||||||
|
|||||||
@ -3,7 +3,8 @@ package spdxhelpers
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/spdx"
|
"github.com/spdx/tools-golang/spdx/common"
|
||||||
|
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@ -12,32 +13,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestToSyftModel(t *testing.T) {
|
func TestToSyftModel(t *testing.T) {
|
||||||
sbom, err := ToSyftModel(&spdx.Document2_2{
|
sbom, err := ToSyftModel(&spdx.Document{
|
||||||
CreationInfo: &spdx.CreationInfo2_2{
|
|
||||||
SPDXVersion: "1",
|
SPDXVersion: "1",
|
||||||
DataLicense: "GPL",
|
DataLicense: "GPL",
|
||||||
SPDXIdentifier: "id-doc-1",
|
SPDXIdentifier: "id-doc-1",
|
||||||
DocumentName: "docName",
|
DocumentName: "docName",
|
||||||
DocumentNamespace: "docNamespace",
|
DocumentNamespace: "docNamespace",
|
||||||
ExternalDocumentReferences: nil,
|
ExternalDocumentReferences: nil,
|
||||||
|
DocumentComment: "",
|
||||||
|
CreationInfo: &spdx.CreationInfo{
|
||||||
LicenseListVersion: "",
|
LicenseListVersion: "",
|
||||||
CreatorPersons: nil,
|
|
||||||
CreatorOrganizations: nil,
|
|
||||||
CreatorTools: nil,
|
|
||||||
Created: "",
|
Created: "",
|
||||||
CreatorComment: "",
|
CreatorComment: "",
|
||||||
DocumentComment: "",
|
|
||||||
},
|
},
|
||||||
Packages: map[spdx.ElementID]*spdx.Package2_2{
|
Packages: []*spdx.Package{
|
||||||
"id-pkg-1": {
|
{
|
||||||
PackageName: "pkg-1",
|
PackageName: "pkg-1",
|
||||||
PackageSPDXIdentifier: "id-pkg-1",
|
PackageSPDXIdentifier: "id-pkg-1",
|
||||||
PackageVersion: "5.4.3",
|
PackageVersion: "5.4.3",
|
||||||
PackageSupplierPerson: "",
|
|
||||||
PackageSupplierOrganization: "",
|
|
||||||
PackageLicenseDeclared: "",
|
PackageLicenseDeclared: "",
|
||||||
PackageDescription: "",
|
PackageDescription: "",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "SECURITY",
|
Category: "SECURITY",
|
||||||
Locator: "cpe:2.3:a:pkg-1:pkg-1:5.4.3:*:*:*:*:*:*:*",
|
Locator: "cpe:2.3:a:pkg-1:pkg-1:5.4.3:*:*:*:*:*:*:*",
|
||||||
@ -49,22 +45,20 @@ func TestToSyftModel(t *testing.T) {
|
|||||||
RefType: "cpe23Type",
|
RefType: "cpe23Type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
|
Locator: "pkg:alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Files: nil,
|
Files: nil,
|
||||||
},
|
},
|
||||||
"id-pkg-2": {
|
{
|
||||||
PackageName: "pkg-2",
|
PackageName: "pkg-2",
|
||||||
PackageSPDXIdentifier: "id-pkg-2",
|
PackageSPDXIdentifier: "id-pkg-2",
|
||||||
PackageVersion: "7.3.1",
|
PackageVersion: "7.3.1",
|
||||||
PackageSupplierPerson: "",
|
|
||||||
PackageSupplierOrganization: "",
|
|
||||||
PackageLicenseDeclared: "",
|
PackageLicenseDeclared: "",
|
||||||
PackageDescription: "",
|
PackageDescription: "",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "SECURITY",
|
Category: "SECURITY",
|
||||||
Locator: "cpe:2.3:a:pkg-2:pkg-2:7.3.1:*:*:*:*:*:*:*",
|
Locator: "cpe:2.3:a:pkg-2:pkg-2:7.3.1:*:*:*:*:*:*:*",
|
||||||
@ -81,7 +75,7 @@ func TestToSyftModel(t *testing.T) {
|
|||||||
RefType: "cpe23Type",
|
RefType: "cpe23Type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=p2-origin@9.1.3&distro=debian-3.10.9",
|
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=p2-origin@9.1.3&distro=debian-3.10.9",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
@ -89,8 +83,7 @@ func TestToSyftModel(t *testing.T) {
|
|||||||
Files: nil,
|
Files: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
UnpackagedFiles: map[spdx.ElementID]*spdx.File2_2{},
|
Relationships: []*spdx.Relationship{},
|
||||||
Relationships: []*spdx.Relationship2_2{},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -120,17 +113,17 @@ func TestToSyftModel(t *testing.T) {
|
|||||||
func Test_extractMetadata(t *testing.T) {
|
func Test_extractMetadata(t *testing.T) {
|
||||||
oneTwoThreeFour := 1234
|
oneTwoThreeFour := 1234
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
pkg spdx.Package2_2
|
pkg spdx.Package
|
||||||
metaType pkg.MetadataType
|
metaType pkg.MetadataType
|
||||||
meta interface{}
|
meta interface{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "SomeDebPkg",
|
PackageName: "SomeDebPkg",
|
||||||
PackageVersion: "43.1.235",
|
PackageVersion: "43.1.235",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=somedebpkg-origin@9.1.3&distro=debian-3.10.9",
|
Locator: "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=somedebpkg-origin@9.1.3&distro=debian-3.10.9",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
@ -146,12 +139,12 @@ func Test_extractMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "SomeApkPkg",
|
PackageName: "SomeApkPkg",
|
||||||
PackageVersion: "3.2.9",
|
PackageVersion: "3.2.9",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
|
Locator: "pkg:alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
@ -166,12 +159,12 @@ func Test_extractMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "SomeRpmPkg",
|
PackageName: "SomeRpmPkg",
|
||||||
PackageVersion: "13.2.79",
|
PackageVersion: "13.2.79",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:rpm/pkg-2@7.3.1?arch=x86_64&epoch=1234&upstream=some-rpm-origin-1.16.3&distro=alpine-3.10.9",
|
Locator: "pkg:rpm/pkg-2@7.3.1?arch=x86_64&epoch=1234&upstream=some-rpm-origin-1.16.3&distro=alpine-3.10.9",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
@ -238,24 +231,24 @@ func TestExtractSourceFromNamespaces(t *testing.T) {
|
|||||||
func TestH1Digest(t *testing.T) {
|
func TestH1Digest(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
pkg spdx.Package2_2
|
pkg spdx.Package
|
||||||
expectedDigest string
|
expectedDigest string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid h1digest",
|
name: "valid h1digest",
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "github.com/googleapis/gnostic",
|
PackageName: "github.com/googleapis/gnostic",
|
||||||
PackageVersion: "v0.5.5",
|
PackageVersion: "v0.5.5",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{
|
PackageChecksums: []common.Checksum{
|
||||||
spdx.SHA256: {
|
{
|
||||||
Algorithm: spdx.SHA256,
|
Algorithm: common.SHA256,
|
||||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -264,19 +257,19 @@ func TestH1Digest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid h1digest algorithm",
|
name: "invalid h1digest algorithm",
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "github.com/googleapis/gnostic",
|
PackageName: "github.com/googleapis/gnostic",
|
||||||
PackageVersion: "v0.5.5",
|
PackageVersion: "v0.5.5",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{
|
PackageChecksums: []common.Checksum{
|
||||||
spdx.SHA256: {
|
{
|
||||||
Algorithm: spdx.SHA1,
|
Algorithm: common.SHA1,
|
||||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -285,19 +278,19 @@ func TestH1Digest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid h1digest digest",
|
name: "invalid h1digest digest",
|
||||||
pkg: spdx.Package2_2{
|
pkg: spdx.Package{
|
||||||
PackageName: "github.com/googleapis/gnostic",
|
PackageName: "github.com/googleapis/gnostic",
|
||||||
PackageVersion: "v0.5.5",
|
PackageVersion: "v0.5.5",
|
||||||
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
|
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||||
{
|
{
|
||||||
Category: "PACKAGE_MANAGER",
|
Category: "PACKAGE-MANAGER",
|
||||||
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
Locator: "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
|
||||||
RefType: "purl",
|
RefType: "purl",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{
|
PackageChecksums: []common.Checksum{
|
||||||
spdx.SHA256: {
|
{
|
||||||
Algorithm: spdx.SHA256,
|
Algorithm: common.SHA256,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/github"
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/formats/table"
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
"github.com/anchore/syft/syft/formats/template"
|
"github.com/anchore/syft/syft/formats/template"
|
||||||
@ -24,8 +24,8 @@ func Formats() []sbom.Format {
|
|||||||
cyclonedxxml.Format(),
|
cyclonedxxml.Format(),
|
||||||
cyclonedxjson.Format(),
|
cyclonedxjson.Format(),
|
||||||
github.Format(),
|
github.Format(),
|
||||||
spdx22tagvalue.Format(),
|
spdxtagvalue.Format(),
|
||||||
spdx22json.Format(),
|
spdxjson.Format(),
|
||||||
table.Format(),
|
table.Format(),
|
||||||
text.Format(),
|
text.Format(),
|
||||||
template.Format(),
|
template.Format(),
|
||||||
@ -61,9 +61,9 @@ func ByName(name string) sbom.Format {
|
|||||||
case "github", "githubjson":
|
case "github", "githubjson":
|
||||||
return ByID(github.ID)
|
return ByID(github.ID)
|
||||||
case "spdx", "spdxtv", "spdxtagvalue":
|
case "spdx", "spdxtv", "spdxtagvalue":
|
||||||
return ByID(spdx22tagvalue.ID)
|
return ByID(spdxtagvalue.ID)
|
||||||
case "spdxjson":
|
case "spdxjson":
|
||||||
return ByID(spdx22json.ID)
|
return ByID(spdxjson.ID)
|
||||||
case "table":
|
case "table":
|
||||||
return ByID(table.ID)
|
return ByID(table.ID)
|
||||||
case "text":
|
case "text":
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/github"
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/formats/table"
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
"github.com/anchore/syft/syft/formats/template"
|
"github.com/anchore/syft/syft/formats/template"
|
||||||
@ -78,41 +78,41 @@ func TestByName(t *testing.T) {
|
|||||||
// SPDX Tag-Value
|
// SPDX Tag-Value
|
||||||
{
|
{
|
||||||
name: "spdx",
|
name: "spdx",
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx-tag-value",
|
name: "spdx-tag-value",
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx-tv",
|
name: "spdx-tv",
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdxtv", // clean variant
|
name: "spdxtv", // clean variant
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx-2-tag-value", // clean variant
|
name: "spdx-2-tag-value", // clean variant
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx-2-tagvalue", // clean variant
|
name: "spdx-2-tagvalue", // clean variant
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx2-tagvalue", // clean variant
|
name: "spdx2-tagvalue", // clean variant
|
||||||
want: spdx22tagvalue.ID,
|
want: spdxtagvalue.ID,
|
||||||
},
|
},
|
||||||
|
|
||||||
// SPDX JSON
|
// SPDX JSON
|
||||||
{
|
{
|
||||||
name: "spdx-json",
|
name: "spdx-json",
|
||||||
want: spdx22json.ID,
|
want: spdxjson.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spdx-2-json",
|
name: "spdx-2-json",
|
||||||
want: spdx22json.ID,
|
want: spdxjson.ID,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Cyclonedx JSON
|
// Cyclonedx JSON
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package spdx22json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/jsonloader"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
)
|
|
||||||
|
|
||||||
func decoder(reader io.Reader) (s *sbom.SBOM, err error) {
|
|
||||||
defer func() {
|
|
||||||
// The spdx tools JSON parser panics in quite a lot of situations, just handle this as a parse failure
|
|
||||||
if v := recover(); v != nil {
|
|
||||||
s = nil
|
|
||||||
err = fmt.Errorf("an error occurred during SPDX JSON document parsing: %+v", v)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
doc, err := jsonloader.Load2_2(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return spdxhelpers.ToSyftModel(doc)
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type AnnotationType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReviewerAnnotationType AnnotationType = "REVIEWER"
|
|
||||||
OtherAnnotationType AnnotationType = "OTHER"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Annotation struct {
|
|
||||||
// Identify when the comment was made. This is to be specified according to the combined date and time in the
|
|
||||||
// UTC format, as specified in the ISO 8601 standard.
|
|
||||||
AnnotationDate time.Time `json:"annotationDate"`
|
|
||||||
// Type of the annotation
|
|
||||||
AnnotationType AnnotationType `json:"annotationType"`
|
|
||||||
// This field identifies the person, organization or tool that has commented on a file, package, or the entire document.
|
|
||||||
Annotator string `json:"annotator"`
|
|
||||||
Comment string `json:"comment"`
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Checksum struct {
|
|
||||||
// Identifies the algorithm used to produce the subject Checksum. One of: "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224"
|
|
||||||
Algorithm string `json:"algorithm"`
|
|
||||||
ChecksumValue string `json:"checksumValue"`
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type CreationInfo struct {
|
|
||||||
Comment string `json:"comment,omitempty"`
|
|
||||||
// Identify when the SPDX file was originally created. The date is to be specified according to combined date and
|
|
||||||
// time in UTC format as specified in ISO 8601 standard. This field is distinct from the fields in section 8,
|
|
||||||
// which involves the addition of information during a subsequent review.
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
// Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an
|
|
||||||
// individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization,
|
|
||||||
// indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version
|
|
||||||
// for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person
|
|
||||||
// name or organization name may be designated as “anonymous” if appropriate.
|
|
||||||
Creators []string `json:"creators"`
|
|
||||||
// An optional field for creators of the SPDX file to provide the version of the SPDX License List used when the SPDX file was created.
|
|
||||||
LicenseListVersion string `json:"licenseListVersion"`
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
// derived from:
|
|
||||||
// - https://spdx.github.io/spdx-spec/appendix-III-RDF-data-model-implementation-and-identifier-syntax/
|
|
||||||
// - https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json
|
|
||||||
// - https://github.com/spdx/spdx-spec/tree/v2.2/ontology
|
|
||||||
|
|
||||||
type Document struct {
|
|
||||||
Element
|
|
||||||
SPDXVersion string `json:"spdxVersion"`
|
|
||||||
// One instance is required for each SPDX file produced. It provides the necessary information for forward
|
|
||||||
// and backward compatibility for processing tools.
|
|
||||||
CreationInfo CreationInfo `json:"creationInfo"`
|
|
||||||
// 2.2: Data License; should be "CC0-1.0"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// License expression for dataLicense. Compliance with the SPDX specification includes populating the SPDX
|
|
||||||
// fields therein with data related to such fields (\"SPDX-Metadata\"). The SPDX specification contains numerous
|
|
||||||
// fields where an SPDX document creator may provide relevant explanatory text in SPDX-Metadata. Without
|
|
||||||
// opining on the lawfulness of \"database rights\" (in jurisdictions where applicable), such explanatory text
|
|
||||||
// is copyrightable subject matter in most Berne Convention countries. By using the SPDX specification, or any
|
|
||||||
// portion hereof, you hereby agree that any copyright rights (as determined by your jurisdiction) in any
|
|
||||||
// SPDX-Metadata, including without limitation explanatory text, shall be subject to the terms of the Creative
|
|
||||||
// Commons CC0 1.0 Universal license. For SPDX-Metadata not containing any copyright rights, you hereby agree
|
|
||||||
// and acknowledge that the SPDX-Metadata is provided to you \"as-is\" and without any representations or
|
|
||||||
// warranties of any kind concerning the SPDX-Metadata, express, implied, statutory or otherwise, including
|
|
||||||
// without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement,
|
|
||||||
// or the absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not
|
|
||||||
// discoverable, all to the greatest extent permissible under applicable law.
|
|
||||||
DataLicense string `json:"dataLicense"`
|
|
||||||
// Information about an external SPDX document reference including the checksum. This allows for verification of the external references.
|
|
||||||
ExternalDocumentRefs []ExternalDocumentRef `json:"externalDocumentRefs,omitempty"`
|
|
||||||
// Indicates that a particular ExtractedLicensingInfo was defined in the subject SpdxDocument.
|
|
||||||
HasExtractedLicensingInfos []HasExtractedLicensingInfo `json:"hasExtractedLicensingInfos,omitempty"`
|
|
||||||
// note: found in example documents from SPDX, but not in the JSON schema. See https://spdx.github.io/spdx-spec/2-document-creation-information/#25-spdx-document-namespace
|
|
||||||
DocumentNamespace string `json:"documentNamespace"`
|
|
||||||
// note: found in example documents from SPDX, but not in the JSON schema
|
|
||||||
// DocumentDescribes []string `json:"documentDescribes"`
|
|
||||||
Packages []Package `json:"packages"`
|
|
||||||
// Files referenced in the SPDX document
|
|
||||||
Files []File `json:"files,omitempty"`
|
|
||||||
// Snippets referenced in the SPDX document
|
|
||||||
Snippets []Snippet `json:"snippets,omitempty"`
|
|
||||||
// Relationships referenced in the SPDX document
|
|
||||||
Relationships []Relationship `json:"relationships,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Element struct {
|
|
||||||
SPDXID string `json:"SPDXID"`
|
|
||||||
// Identify name of this SpdxElement.
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
// Relationships referenced in the SPDX document
|
|
||||||
Relationships []Relationship `json:"relationships,omitempty"`
|
|
||||||
// Provide additional information about an SpdxElement.
|
|
||||||
Annotations []Annotation `json:"annotations,omitempty"`
|
|
||||||
Comment string `json:"comment,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/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-" + spdxhelpers.SanitizeElementID(string(e))
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type ExternalDocumentRef struct {
|
|
||||||
// externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.
|
|
||||||
ExternalDocumentID string `json:"externalDocumentId"`
|
|
||||||
Checksum Checksum `json:"checksum"`
|
|
||||||
// SPDX ID for SpdxDocument. A propoerty containing an SPDX document.
|
|
||||||
SpdxDocument string `json:"spdxDocument"`
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Item
|
|
||||||
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
|
|
||||||
// contents of a File or Package have not changed.
|
|
||||||
Checksums []Checksum `json:"checksums,omitempty"`
|
|
||||||
// This field provides a place for the SPDX file creator to record file contributors. Contributors could include
|
|
||||||
// names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.
|
|
||||||
FileContributors []string `json:"fileContributors,omitempty"`
|
|
||||||
// Each element is a SPDX ID for a File.
|
|
||||||
FileDependencies []string `json:"fileDependencies,omitempty"`
|
|
||||||
// The name of the file relative to the root of the package.
|
|
||||||
FileName string `json:"fileName"`
|
|
||||||
// The type of the file
|
|
||||||
FileTypes []string `json:"fileTypes,omitempty"`
|
|
||||||
// This field provides a place for the SPDX file creator to record potential legal notices found in the file.
|
|
||||||
// This may or may not include copyright statements.
|
|
||||||
NoticeText string `json:"noticeText,omitempty"`
|
|
||||||
// Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name
|
|
||||||
// properties and the URI (if one is known) of doap:Project resources that are values of this property. All other
|
|
||||||
// properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or
|
|
||||||
// from some SPDX formats (deprecated).
|
|
||||||
ArtifactOf []string `json:"artifactOf,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type HasExtractedLicensingInfo struct {
|
|
||||||
// Verbatim license or licensing notice text that was discovered.
|
|
||||||
ExtractedText string `json:"extractedText"`
|
|
||||||
// A human readable short form license identifier for a license. The license ID is iether on the standard license
|
|
||||||
// oist or the form \"LicenseRef-\"[idString] where [idString] is a unique string containing letters,
|
|
||||||
// numbers, \".\", \"-\" or \"+\".
|
|
||||||
LicenseID string `json:"licenseId"`
|
|
||||||
Comment string `json:"comment,omitempty"`
|
|
||||||
// Identify name of this SpdxElement.
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
SeeAlsos []string `json:"seeAlsos,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Element
|
|
||||||
// The licenseComments property allows the preparer of the SPDX document to describe why the licensing in
|
|
||||||
// spdx:licenseConcluded was chosen.
|
|
||||||
LicenseComments string `json:"licenseComments,omitempty"`
|
|
||||||
LicenseConcluded string `json:"licenseConcluded"`
|
|
||||||
// The licensing information that was discovered directly within the package. There will be an instance of this
|
|
||||||
// property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.
|
|
||||||
LicenseInfoFromFiles []string `json:"licenseInfoFromFiles,omitempty"`
|
|
||||||
// Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.
|
|
||||||
LicenseInfoInFiles []string `json:"licenseInfoInFiles,omitempty"`
|
|
||||||
// The text of copyright declarations recited in the Package or File.
|
|
||||||
CopyrightText string `json:"copyrightText,omitempty"`
|
|
||||||
// This field provides a place for the SPDX data creator to record acknowledgements that may be required to be
|
|
||||||
// communicated in some contexts. This is not meant to include the actual complete license text (see
|
|
||||||
// licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText).
|
|
||||||
// The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from
|
|
||||||
// license texts, which may be necessary or desirable to reproduce.
|
|
||||||
AttributionTexts []string `json:"attributionTexts,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
|
|
||||||
type Package struct {
|
|
||||||
Item
|
|
||||||
// The checksum property provides a mechanism that can be used to verify that the contents of a File or
|
|
||||||
// Package have not changed.
|
|
||||||
Checksums []Checksum `json:"checksums,omitempty"`
|
|
||||||
// Provides a detailed description of the package.
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
// The URI at which this package is available for download. Private (i.e., not publicly reachable) URIs are
|
|
||||||
// acceptable as values of this property. The values http://spdx.org/rdf/terms#none and http://spdx.org/rdf/terms#noassertion
|
|
||||||
// may be used to specify that the package is not downloadable or that no attempt was made to determine its
|
|
||||||
// download location, respectively.
|
|
||||||
DownloadLocation string `json:"downloadLocation,omitempty"`
|
|
||||||
// An External Reference allows a Package to reference an external source of additional information, metadata,
|
|
||||||
// enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.
|
|
||||||
ExternalRefs []spdxhelpers.ExternalRef `json:"externalRefs,omitempty"`
|
|
||||||
// Indicates whether the file content of this package has been available for or subjected to analysis when
|
|
||||||
// creating the SPDX document. If false indicates packages that represent metadata or URI references to a
|
|
||||||
// project, product, artifact, distribution or a component. If set to false, the package must not contain any files
|
|
||||||
FilesAnalyzed bool `json:"filesAnalyzed"`
|
|
||||||
// Indicates that a particular file belongs to a package (elements are SPDX ID for a File).
|
|
||||||
HasFiles []string `json:"hasFiles,omitempty"`
|
|
||||||
// Provide a place for the SPDX file creator to record a web site that serves as the package's home page.
|
|
||||||
// This link can also be used to reference further information about the package referenced by the SPDX file creator.
|
|
||||||
Homepage string `json:"homepage,omitempty"`
|
|
||||||
// List the licenses that have been declared by the authors of the package. Any license information that does not
|
|
||||||
// originate from the package authors, e.g. license information from a third party repository, should not be included in this field.
|
|
||||||
LicenseDeclared string `json:"licenseDeclared"`
|
|
||||||
// The name and, optionally, contact information of the person or organization that originally created the package.
|
|
||||||
// Values of this property must conform to the agent and tool syntax.
|
|
||||||
Originator string `json:"originator,omitempty"`
|
|
||||||
// The base name of the package file name. For example, zlib-1.2.5.tar.gz.
|
|
||||||
PackageFileName string `json:"packageFileName,omitempty"`
|
|
||||||
// A manifest based verification code (the algorithm is defined in section 4.7 of the full specification) of the
|
|
||||||
// SPDX Item. This allows consumers of this data and/or database to determine if an SPDX item they have in hand
|
|
||||||
// is identical to the SPDX item from which the data was produced. This algorithm works even if the SPDX document
|
|
||||||
// is included in the SPDX item.
|
|
||||||
PackageVerificationCode *PackageVerificationCode `json:"packageVerificationCode,omitempty"`
|
|
||||||
// Allows the producer(s) of the SPDX document to describe how the package was acquired and/or changed from the original source.
|
|
||||||
SourceInfo string `json:"sourceInfo,omitempty"`
|
|
||||||
// Provides a short description of the package.
|
|
||||||
Summary string `json:"summary,omitempty"`
|
|
||||||
// The name and, optionally, contact information of the person or organization who was the immediate supplier
|
|
||||||
// of this package to the recipient. The supplier may be different than originator when the software has been
|
|
||||||
// repackaged. Values of this property must conform to the agent and tool syntax.
|
|
||||||
Supplier string `json:"supplier,omitempty"`
|
|
||||||
// Provides an indication of the version of the package that is described by this SpdxDocument.
|
|
||||||
VersionInfo string `json:"versionInfo,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
// Why are there two package identifier fields Package Checksum and Package Verification?
|
|
||||||
// Although the values of the two fields Package Checksum and Package Verification are similar, they each serve a
|
|
||||||
// different purpose. The Package Checksum provides a unique identifier of a software package which is computed by
|
|
||||||
// taking the SHA1 of the entire software package file. This enables one to quickly determine if two different copies
|
|
||||||
// of a package are the same. One disadvantage of this approach is that one cannot add an SPDX data file into the
|
|
||||||
// original package without changing the Package Checksum value. Alternatively, the Package Verification field enables
|
|
||||||
// the inclusion of an SPDX file. It enables one to quickly verify if one or more of the original package files has
|
|
||||||
// changed. The Package Verification field is a unique identifier that is based on SHAing only the original package
|
|
||||||
// files (e.g., excluding the SPDX file). This allows one to add an SPDX file to the original package without changing
|
|
||||||
// this unique identifier.
|
|
||||||
// source: https://wiki.spdx.org/view/SPDX_FAQ
|
|
||||||
type PackageVerificationCode struct {
|
|
||||||
// "A file that was excluded when calculating the package verification code. This is usually a file containing
|
|
||||||
// SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded
|
|
||||||
// from the package verification code. If this is not done it would be impossible to correctly calculate the
|
|
||||||
// verification codes in both files.
|
|
||||||
PackageVerificationCodeExcludedFiles []string `json:"packageVerificationCodeExcludedFiles"`
|
|
||||||
|
|
||||||
// The actual package verification code as a hex encoded value.
|
|
||||||
PackageVerificationCodeValue string `json:"packageVerificationCodeValue"`
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
|
|
||||||
type Relationship struct {
|
|
||||||
// Id to which the SPDX element is related
|
|
||||||
SpdxElementID string `json:"spdxElementId"`
|
|
||||||
// Describes the type of relationship between two SPDX elements.
|
|
||||||
RelationshipType spdxhelpers.RelationshipType `json:"relationshipType"`
|
|
||||||
// SPDX ID for SpdxElement. A related SpdxElement.
|
|
||||||
RelatedSpdxElement string `json:"relatedSpdxElement"`
|
|
||||||
Comment string `json:"comment,omitempty"`
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type StartPointer struct {
|
|
||||||
Offset int `json:"offset,omitempty"`
|
|
||||||
LineNumber int `json:"lineNumber,omitempty"`
|
|
||||||
// SPDX ID for File
|
|
||||||
Reference string `json:"reference"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndPointer struct {
|
|
||||||
Offset int `json:"offset,omitempty"`
|
|
||||||
LineNumber int `json:"lineNumber,omitempty"`
|
|
||||||
// SPDX ID for File
|
|
||||||
Reference string `json:"reference"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Range struct {
|
|
||||||
StartPointer StartPointer `json:"startPointer"`
|
|
||||||
EndPointer EndPointer `json:"endPointer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Snippet struct {
|
|
||||||
Item
|
|
||||||
// Licensing information that was discovered directly in the subject snippet. This is also considered a declared
|
|
||||||
// license for the snippet. (elements are license expressions)
|
|
||||||
LicenseInfoInSnippets []string `json:"licenseInfoInSnippets"`
|
|
||||||
// SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).
|
|
||||||
SnippetFromFile string `json:"snippetFromFile"`
|
|
||||||
// (At least 1 range is required). This field defines the byte range in the original host file (in X.2) that the
|
|
||||||
// snippet information applies to.
|
|
||||||
Ranges []Range `json:"ranges"`
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
const Version = "SPDX-2.2"
|
|
||||||
@ -1,268 +0,0 @@
|
|||||||
package spdx22json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
|
||||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
"github.com/anchore/syft/syft/formats/common/util"
|
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json/model"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
"github.com/anchore/syft/syft/source"
|
|
||||||
)
|
|
||||||
|
|
||||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
|
||||||
func toFormatModel(s sbom.SBOM) *model.Document {
|
|
||||||
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
|
||||||
|
|
||||||
relationships := s.RelationshipsSorted()
|
|
||||||
|
|
||||||
return &model.Document{
|
|
||||||
Element: model.Element{
|
|
||||||
SPDXID: model.ElementID("DOCUMENT").String(),
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
SPDXVersion: model.Version,
|
|
||||||
CreationInfo: model.CreationInfo{
|
|
||||||
Created: time.Now().UTC(),
|
|
||||||
Creators: []string{
|
|
||||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
|
||||||
"Organization: Anchore, Inc",
|
|
||||||
"Tool: " + internal.ApplicationName + "-" + s.Descriptor.Version,
|
|
||||||
},
|
|
||||||
LicenseListVersion: spdxlicense.Version,
|
|
||||||
},
|
|
||||||
DataLicense: "CC0-1.0",
|
|
||||||
DocumentNamespace: namespace,
|
|
||||||
Packages: toPackages(s.Artifacts.PackageCatalog, relationships),
|
|
||||||
Files: toFiles(s),
|
|
||||||
Relationships: toRelationships(relationships),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []model.Package {
|
|
||||||
packages := make([]model.Package, 0)
|
|
||||||
|
|
||||||
for _, p := range catalog.Sorted() {
|
|
||||||
license := spdxhelpers.License(p)
|
|
||||||
packageSpdxID := model.ElementID(p.ID()).String()
|
|
||||||
checksums, filesAnalyzed := toPackageChecksums(p)
|
|
||||||
|
|
||||||
// note: the license concluded and declared should be the same since we are collecting license information
|
|
||||||
// from the project data itself (the installed package files).
|
|
||||||
packages = append(packages, model.Package{
|
|
||||||
Checksums: checksums,
|
|
||||||
Description: spdxhelpers.Description(p),
|
|
||||||
DownloadLocation: spdxhelpers.DownloadLocation(p),
|
|
||||||
ExternalRefs: spdxhelpers.ExternalRefs(p),
|
|
||||||
FilesAnalyzed: filesAnalyzed,
|
|
||||||
HasFiles: fileIDsForPackage(packageSpdxID, relationships),
|
|
||||||
Homepage: spdxhelpers.Homepage(p),
|
|
||||||
// The Declared License is what the authors of a project believe govern the package
|
|
||||||
LicenseDeclared: license,
|
|
||||||
Originator: spdxhelpers.Originator(p),
|
|
||||||
SourceInfo: spdxhelpers.SourceInfo(p),
|
|
||||||
VersionInfo: p.Version,
|
|
||||||
Item: model.Item{
|
|
||||||
// The Concluded License field is the license the SPDX file creator believes governs the package
|
|
||||||
LicenseConcluded: license,
|
|
||||||
Element: model.Element{
|
|
||||||
SPDXID: packageSpdxID,
|
|
||||||
Name: p.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return packages
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPackageChecksums(p pkg.Package) ([]model.Checksum, bool) {
|
|
||||||
filesAnalyzed := false
|
|
||||||
var checksums []model.Checksum
|
|
||||||
switch meta := p.Metadata.(type) {
|
|
||||||
// we generate digest for some Java packages
|
|
||||||
// see page 33 of the spdx specification for 2.2
|
|
||||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
|
||||||
case pkg.JavaMetadata:
|
|
||||||
if len(meta.ArchiveDigests) > 0 {
|
|
||||||
filesAnalyzed = true
|
|
||||||
for _, digest := range meta.ArchiveDigests {
|
|
||||||
checksums = append(checksums, model.Checksum{
|
|
||||||
Algorithm: strings.ToUpper(digest.Algorithm),
|
|
||||||
ChecksumValue: digest.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case pkg.GolangBinMetadata:
|
|
||||||
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("invalid h1digest: %s: %v", meta.H1Digest, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
algo = strings.ToUpper(algo)
|
|
||||||
checksums = append(checksums, model.Checksum{
|
|
||||||
Algorithm: strings.ToUpper(algo),
|
|
||||||
ChecksumValue: hexStr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return checksums, filesAnalyzed
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationship) (fileIDs []string) {
|
|
||||||
for _, relationship := range relationships {
|
|
||||||
if relationship.Type != artifact.ContainsRelationship {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := relationship.From.(pkg.Package); !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := relationship.To.(source.Coordinates); !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
from := model.ElementID(relationship.From.ID()).String()
|
|
||||||
if from == packageSpdxID {
|
|
||||||
to := model.ElementID(relationship.To.ID()).String()
|
|
||||||
fileIDs = append(fileIDs, to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileIDs
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFiles(s sbom.SBOM) []model.File {
|
|
||||||
results := make([]model.File, 0)
|
|
||||||
artifacts := s.Artifacts
|
|
||||||
|
|
||||||
for _, coordinates := range s.AllCoordinates() {
|
|
||||||
var metadata *source.FileMetadata
|
|
||||||
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
|
|
||||||
metadata = &metadataForLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
var digests []file.Digest
|
|
||||||
if digestsForLocation, exists := artifacts.FileDigests[coordinates]; exists {
|
|
||||||
digests = digestsForLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add file classifications (?) and content as a snippet
|
|
||||||
|
|
||||||
var comment string
|
|
||||||
if coordinates.FileSystemID != "" {
|
|
||||||
comment = fmt.Sprintf("layerID: %s", coordinates.FileSystemID)
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, model.File{
|
|
||||||
Item: model.Item{
|
|
||||||
Element: model.Element{
|
|
||||||
SPDXID: model.ElementID(coordinates.ID()).String(),
|
|
||||||
Comment: comment,
|
|
||||||
},
|
|
||||||
// required, no attempt made to determine license information
|
|
||||||
LicenseConcluded: "NOASSERTION",
|
|
||||||
},
|
|
||||||
Checksums: toFileChecksums(digests),
|
|
||||||
FileName: coordinates.RealPath,
|
|
||||||
FileTypes: toFileTypes(metadata),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by real path then virtual path to ensure the result is stable across multiple runs
|
|
||||||
sort.SliceStable(results, func(i, j int) bool {
|
|
||||||
if results[i].FileName == results[j].FileName {
|
|
||||||
return results[i].SPDXID < results[j].SPDXID
|
|
||||||
}
|
|
||||||
return results[i].FileName < results[j].FileName
|
|
||||||
})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFileChecksums(digests []file.Digest) (checksums []model.Checksum) {
|
|
||||||
for _, digest := range digests {
|
|
||||||
checksums = append(checksums, model.Checksum{
|
|
||||||
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
|
||||||
ChecksumValue: digest.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return checksums
|
|
||||||
}
|
|
||||||
|
|
||||||
func toChecksumAlgorithm(algorithm string) string {
|
|
||||||
// basically, we need an uppercase version of our algorithm:
|
|
||||||
// https://github.com/spdx/spdx-spec/blob/development/v2.2.2/schemas/spdx-schema.json#L165
|
|
||||||
return strings.ToUpper(algorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
|
||||||
if metadata == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mimeTypePrefix := strings.Split(metadata.MIMEType, "/")[0]
|
|
||||||
switch mimeTypePrefix {
|
|
||||||
case "image":
|
|
||||||
ty = append(ty, string(spdxhelpers.ImageFileType))
|
|
||||||
case "video":
|
|
||||||
ty = append(ty, string(spdxhelpers.VideoFileType))
|
|
||||||
case "application":
|
|
||||||
ty = append(ty, string(spdxhelpers.ApplicationFileType))
|
|
||||||
case "text":
|
|
||||||
ty = append(ty, string(spdxhelpers.TextFileType))
|
|
||||||
case "audio":
|
|
||||||
ty = append(ty, string(spdxhelpers.AudioFileType))
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.IsExecutable(metadata.MIMEType) {
|
|
||||||
ty = append(ty, string(spdxhelpers.BinaryFileType))
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.IsArchive(metadata.MIMEType) {
|
|
||||||
ty = append(ty, string(spdxhelpers.ArchiveFileType))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add support for source, spdx, and documentation file types
|
|
||||||
if len(ty) == 0 {
|
|
||||||
ty = append(ty, string(spdxhelpers.OtherFileType))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ty
|
|
||||||
}
|
|
||||||
|
|
||||||
func toRelationships(relationships []artifact.Relationship) (result []model.Relationship) {
|
|
||||||
for _, r := range relationships {
|
|
||||||
exists, relationshipType, comment := lookupRelationship(r.Type)
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
log.Warnf("unable to convert relationship from SPDX 2.2 JSON, dropping: %+v", r)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, model.Relationship{
|
|
||||||
SpdxElementID: model.ElementID(r.From.ID()).String(),
|
|
||||||
RelationshipType: relationshipType,
|
|
||||||
RelatedSpdxElement: model.ElementID(r.To.ID()).String(),
|
|
||||||
Comment: comment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupRelationship(ty artifact.RelationshipType) (bool, spdxhelpers.RelationshipType, string) {
|
|
||||||
switch ty {
|
|
||||||
case artifact.ContainsRelationship:
|
|
||||||
return true, spdxhelpers.ContainsRelationship, ""
|
|
||||||
case artifact.OwnershipByFileOverlapRelationship:
|
|
||||||
return true, spdxhelpers.OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
|
||||||
}
|
|
||||||
return false, "", ""
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package spdx22tagvalue
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/tvsaver"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
|
||||||
model := toFormatModel(s)
|
|
||||||
return tvsaver.Save2_2(model, output)
|
|
||||||
}
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
package spdx22tagvalue
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spdx/tools-golang/spdx"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
"github.com/anchore/syft/internal/spdxlicense"
|
|
||||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
|
||||||
"github.com/anchore/syft/syft/formats/common/util"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
)
|
|
||||||
|
|
||||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
|
||||||
func toFormatModel(s sbom.SBOM) *spdx.Document2_2 {
|
|
||||||
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)
|
|
||||||
|
|
||||||
return &spdx.Document2_2{
|
|
||||||
CreationInfo: &spdx.CreationInfo2_2{
|
|
||||||
// 2.1: SPDX Version; should be in the format "SPDX-2.2"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
SPDXVersion: "SPDX-2.2",
|
|
||||||
|
|
||||||
// 2.2: Data License; should be "CC0-1.0"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
DataLicense: "CC0-1.0",
|
|
||||||
|
|
||||||
// 2.3: SPDX Identifier; should be "DOCUMENT" to represent mandatory identifier of SPDXRef-DOCUMENT
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
SPDXIdentifier: spdx.ElementID("DOCUMENT"),
|
|
||||||
|
|
||||||
// 2.4: Document Name
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
DocumentName: name,
|
|
||||||
|
|
||||||
// 2.5: Document Namespace
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// Purpose: Provide an SPDX document specific namespace as a unique absolute Uniform Resource
|
|
||||||
// Identifier (URI) as specified in RFC-3986, with the exception of the ‘#’ delimiter. The SPDX
|
|
||||||
// Document URI cannot contain a URI "part" (e.g. the "#" character), since the ‘#’ is used in SPDX
|
|
||||||
// element URIs (packages, files, snippets, etc) to separate the document namespace from the
|
|
||||||
// element’s SPDX identifier. Additionally, a scheme (e.g. “https:”) is required.
|
|
||||||
|
|
||||||
// The URI must be unique for the SPDX document including the specific version of the SPDX document.
|
|
||||||
// If the SPDX document is updated, thereby creating a new version, a new URI for the updated
|
|
||||||
// document must be used. There can only be one URI for an SPDX document and only one SPDX document
|
|
||||||
// for a given URI.
|
|
||||||
|
|
||||||
// Note that the URI does not have to be accessible. It is only intended to provide a unique ID.
|
|
||||||
// In many cases, the URI will point to a web accessible document, but this should not be assumed
|
|
||||||
// to be the case.
|
|
||||||
|
|
||||||
DocumentNamespace: namespace,
|
|
||||||
|
|
||||||
// 2.6: External Document References
|
|
||||||
// Cardinality: optional, one or many
|
|
||||||
ExternalDocumentReferences: nil,
|
|
||||||
|
|
||||||
// 2.7: License List Version
|
|
||||||
// Cardinality: optional, one
|
|
||||||
LicenseListVersion: spdxlicense.Version,
|
|
||||||
|
|
||||||
// 2.8: Creators: may have multiple keys for Person, Organization
|
|
||||||
// and/or Tool
|
|
||||||
// Cardinality: mandatory, one or many
|
|
||||||
CreatorPersons: nil,
|
|
||||||
CreatorOrganizations: []string{"Anchore, Inc"},
|
|
||||||
CreatorTools: []string{internal.ApplicationName + "-" + s.Descriptor.Version},
|
|
||||||
|
|
||||||
// 2.9: Created: data format YYYY-MM-DDThh:mm:ssZ
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
Created: time.Now().UTC().Format(time.RFC3339),
|
|
||||||
|
|
||||||
// 2.10: Creator Comment
|
|
||||||
// Cardinality: optional, one
|
|
||||||
CreatorComment: "",
|
|
||||||
|
|
||||||
// 2.11: Document Comment
|
|
||||||
// Cardinality: optional, one
|
|
||||||
DocumentComment: "",
|
|
||||||
},
|
|
||||||
Packages: toFormatPackages(s.Artifacts.PackageCatalog),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
|
||||||
//
|
|
||||||
//nolint:funlen
|
|
||||||
func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2 {
|
|
||||||
results := make(map[spdx.ElementID]*spdx.Package2_2)
|
|
||||||
|
|
||||||
for _, p := range catalog.Sorted() {
|
|
||||||
// name should be guaranteed to be unique, but semantically useful and stable
|
|
||||||
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
|
|
||||||
// the Comments on License field (section 3.16) is preferred.
|
|
||||||
license := spdxhelpers.License(p)
|
|
||||||
checksums, filesAnalyzed := toPackageChecksums(p)
|
|
||||||
|
|
||||||
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
|
||||||
|
|
||||||
// NOT PART OF SPEC
|
|
||||||
// flag: does this "package" contain files that were in fact "unpackaged",
|
|
||||||
// e.g. included directly in the Document without being in a Package?
|
|
||||||
IsUnpackaged: false,
|
|
||||||
|
|
||||||
// 3.1: Package Name
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
PackageName: p.Name,
|
|
||||||
|
|
||||||
// 3.2: Package SPDX Identifier: "SPDXRef-[idstring]"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
PackageSPDXIdentifier: spdx.ElementID(id),
|
|
||||||
|
|
||||||
// 3.3: Package Version
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageVersion: p.Version,
|
|
||||||
|
|
||||||
// 3.4: Package File Name
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageFileName: "",
|
|
||||||
|
|
||||||
// 3.5: Package Supplier: may have single result for either Person or Organization,
|
|
||||||
// or NOASSERTION
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageSupplierPerson: "",
|
|
||||||
PackageSupplierOrganization: "",
|
|
||||||
PackageSupplierNOASSERTION: false,
|
|
||||||
|
|
||||||
// 3.6: Package Originator: may have single result for either Person or Organization,
|
|
||||||
// or NOASSERTION
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageOriginatorPerson: "",
|
|
||||||
PackageOriginatorOrganization: "",
|
|
||||||
PackageOriginatorNOASSERTION: false,
|
|
||||||
|
|
||||||
// 3.7: Package Download Location
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// NONE if there is no download location whatsoever.
|
|
||||||
// NOASSERTION if:
|
|
||||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
|
||||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
|
||||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
|
||||||
PackageDownloadLocation: "NOASSERTION",
|
|
||||||
|
|
||||||
// 3.8: FilesAnalyzed
|
|
||||||
// Cardinality: optional, one; default value is "true" if omitted
|
|
||||||
|
|
||||||
// Purpose: Indicates whether the file content of this package has been available for or subjected to
|
|
||||||
// analysis when creating the SPDX document. If false, indicates packages that represent metadata or
|
|
||||||
// URI references to a project, product, artifact, distribution or a component. If false, the package
|
|
||||||
// must not contain any files.
|
|
||||||
|
|
||||||
// Intent: A package can refer to a project, product, artifact, distribution or a component that is
|
|
||||||
// external to the SPDX document.
|
|
||||||
FilesAnalyzed: filesAnalyzed,
|
|
||||||
// NOT PART OF SPEC: did FilesAnalyzed tag appear?
|
|
||||||
IsFilesAnalyzedTagPresent: true,
|
|
||||||
|
|
||||||
// 3.9: Package Verification Code
|
|
||||||
// Cardinality: optional, one if filesAnalyzed is true / omitted;
|
|
||||||
// zero (must be omitted) if filesAnalyzed is false
|
|
||||||
PackageVerificationCode: "",
|
|
||||||
// Spec also allows specifying a single file to exclude from the
|
|
||||||
// verification code algorithm; intended to enable exclusion of
|
|
||||||
// the SPDX document file itself.
|
|
||||||
PackageVerificationCodeExcludedFile: "",
|
|
||||||
|
|
||||||
// 3.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5
|
|
||||||
// Cardinality: optional, one or many
|
|
||||||
|
|
||||||
// 3.10.1 Purpose: Provide an independently reproducible mechanism that permits unique identification of
|
|
||||||
// a specific package that correlates to the data in this SPDX file. This identifier enables a recipient
|
|
||||||
// to determine if any file in the original package has been changed. If the SPDX file is to be included
|
|
||||||
// in a package, this value should not be calculated. The SHA-1 algorithm will be used to provide the
|
|
||||||
// checksum by default.
|
|
||||||
PackageChecksums: checksums,
|
|
||||||
|
|
||||||
// note: based on the purpose above no discovered checksums should be provided, but instead, only
|
|
||||||
// tool-derived checksums.
|
|
||||||
//FIXME: this got removed between 0.1.0 and 0.2.0, is this right? it looks like
|
|
||||||
// it wasn't being used anyway
|
|
||||||
//PackageChecksumSHA1: "",
|
|
||||||
//PackageChecksumSHA256: "",
|
|
||||||
//PackageChecksumMD5: "",
|
|
||||||
|
|
||||||
// 3.11: Package Home Page
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageHomePage: "",
|
|
||||||
|
|
||||||
// 3.12: Source Information
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageSourceInfo: "",
|
|
||||||
|
|
||||||
// 3.13: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// Purpose: Contain the license the SPDX file creator has concluded as governing the
|
|
||||||
// package or alternative values, if the governing license cannot be determined.
|
|
||||||
PackageLicenseConcluded: license,
|
|
||||||
|
|
||||||
// 3.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
|
|
||||||
// Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
|
|
||||||
// zero (must be omitted) if filesAnalyzed is false
|
|
||||||
PackageLicenseInfoFromFiles: nil,
|
|
||||||
|
|
||||||
// 3.15: Declared License: SPDX License Expression, "NONE" or "NOASSERTION"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// Purpose: List the licenses that have been declared by the authors of the package.
|
|
||||||
// Any license information that does not originate from the package authors, e.g. license
|
|
||||||
// information from a third party repository, should not be included in this field.
|
|
||||||
PackageLicenseDeclared: license,
|
|
||||||
|
|
||||||
// 3.16: Comments on License
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageLicenseComments: "",
|
|
||||||
|
|
||||||
// 3.17: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION"
|
|
||||||
// Cardinality: mandatory, one
|
|
||||||
// Purpose: IdentifyFormat the copyright holders of the package, as well as any dates present. This will be a free form text field extracted from package information files. The options to populate this field are limited to:
|
|
||||||
//
|
|
||||||
// Any text related to a copyright notice, even if not complete;
|
|
||||||
// NONE if the package contains no copyright information whatsoever; or
|
|
||||||
// NOASSERTION, if
|
|
||||||
// (i) the SPDX document creator has made no attempt to determine this field; or
|
|
||||||
// (ii) the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
|
||||||
//
|
|
||||||
PackageCopyrightText: "NOASSERTION",
|
|
||||||
|
|
||||||
// 3.18: Package Summary Description
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageSummary: "",
|
|
||||||
|
|
||||||
// 3.19: Package Detailed Description
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageDescription: "",
|
|
||||||
|
|
||||||
// 3.20: Package Comment
|
|
||||||
// Cardinality: optional, one
|
|
||||||
PackageComment: "",
|
|
||||||
|
|
||||||
// 3.21: Package External Reference
|
|
||||||
// Cardinality: optional, one or many
|
|
||||||
PackageExternalReferences: formatSPDXExternalRefs(p),
|
|
||||||
|
|
||||||
// 3.22: Package External Reference Comment
|
|
||||||
// Cardinality: conditional (optional, one) for each External Reference
|
|
||||||
// contained within PackageExternalReference2_1 struct, if present
|
|
||||||
|
|
||||||
// 3.23: Package Attribution Text
|
|
||||||
// Cardinality: optional, one or many
|
|
||||||
PackageAttributionTexts: nil,
|
|
||||||
|
|
||||||
// Files contained in this Package
|
|
||||||
Files: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPackageChecksums(p pkg.Package) (map[spdx.ChecksumAlgorithm]spdx.Checksum, bool) {
|
|
||||||
filesAnalyzed := false
|
|
||||||
checksums := map[spdx.ChecksumAlgorithm]spdx.Checksum{}
|
|
||||||
switch meta := p.Metadata.(type) {
|
|
||||||
// we generate digest for some Java packages
|
|
||||||
// see page 33 of the spdx specification for 2.2
|
|
||||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
|
||||||
case pkg.JavaMetadata:
|
|
||||||
if len(meta.ArchiveDigests) > 0 {
|
|
||||||
filesAnalyzed = true
|
|
||||||
for _, digest := range meta.ArchiveDigests {
|
|
||||||
checksums[spdx.ChecksumAlgorithm(digest.Algorithm)] = spdx.Checksum{
|
|
||||||
Algorithm: spdx.ChecksumAlgorithm(digest.Algorithm),
|
|
||||||
Value: digest.Value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case pkg.GolangBinMetadata:
|
|
||||||
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("invalid h1digest: %s: %v", meta.H1Digest, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
algo = strings.ToUpper(algo)
|
|
||||||
checksums[spdx.ChecksumAlgorithm(algo)] = spdx.Checksum{
|
|
||||||
Algorithm: spdx.ChecksumAlgorithm(algo),
|
|
||||||
Value: hexStr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return checksums, filesAnalyzed
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
|
||||||
for _, ref := range spdxhelpers.ExternalRefs(p) {
|
|
||||||
refs = append(refs, &spdx.PackageExternalReference2_2{
|
|
||||||
Category: string(ref.ReferenceCategory),
|
|
||||||
RefType: string(ref.ReferenceType),
|
|
||||||
Locator: ref.ReferenceLocator,
|
|
||||||
ExternalRefComment: ref.Comment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return refs
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
package spdx22tagvalue
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_H1Digest(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pkg pkg.Package
|
|
||||||
expectedDigest string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid h1digest",
|
|
||||||
pkg: pkg.Package{
|
|
||||||
Name: "github.com/googleapis/gnostic",
|
|
||||||
Version: "v0.5.5",
|
|
||||||
MetadataType: pkg.GolangBinMetadataType,
|
|
||||||
Metadata: pkg.GolangBinMetadata{
|
|
||||||
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedDigest: "SHA256:f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid h1digest",
|
|
||||||
pkg: pkg.Package{
|
|
||||||
Name: "github.com/googleapis/gnostic",
|
|
||||||
Version: "v0.5.5",
|
|
||||||
MetadataType: pkg.GolangBinMetadataType,
|
|
||||||
Metadata: pkg.GolangBinMetadata{
|
|
||||||
H1Digest: "h1:9fHAtK0uzzz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedDigest: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unsupported h-digest",
|
|
||||||
pkg: pkg.Package{
|
|
||||||
Name: "github.com/googleapis/gnostic",
|
|
||||||
Version: "v0.5.5",
|
|
||||||
MetadataType: pkg.GolangBinMetadataType,
|
|
||||||
Metadata: pkg.GolangBinMetadata{
|
|
||||||
H1Digest: "h12:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedDigest: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
catalog := pkg.NewCatalog(test.pkg)
|
|
||||||
pkgs := toFormatPackages(catalog)
|
|
||||||
require.Len(t, pkgs, 1)
|
|
||||||
for _, p := range pkgs {
|
|
||||||
if test.expectedDigest == "" {
|
|
||||||
require.Len(t, p.PackageChecksums, 0)
|
|
||||||
} else {
|
|
||||||
require.Len(t, p.PackageChecksums, 1)
|
|
||||||
for _, c := range p.PackageChecksums {
|
|
||||||
require.Equal(t, test.expectedDigest, fmt.Sprintf("%s:%s", c.Algorithm, c.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
syft/formats/spdxjson/decoder.go
Normal file
20
syft/formats/spdxjson/decoder.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package spdxjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
spdx "github.com/spdx/tools-golang/json"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoder(reader io.Reader) (s *sbom.SBOM, err error) {
|
||||||
|
doc, err := spdx.Load2_3(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spdxhelpers.ToSyftModel(doc)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22json
|
package spdxjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
@ -60,15 +61,15 @@ func TestSPDXJSONDecoder(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.path, func(t *testing.T) {
|
t.Run(test.path, func(t *testing.T) {
|
||||||
f, err := os.Open("test-fixtures/spdx/" + test.path)
|
f, err := os.Open("test-fixtures/spdx/" + test.path)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sbom, err := decoder(f)
|
sbom, err := decoder(f)
|
||||||
|
|
||||||
if test.fail {
|
if test.fail {
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.packages != nil {
|
if test.packages != nil {
|
||||||
@ -1,14 +1,15 @@
|
|||||||
package spdx22json
|
package spdxjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
)
|
)
|
||||||
|
|
||||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||||
doc := toFormatModel(s)
|
doc := spdxhelpers.ToFormatModel(s)
|
||||||
|
|
||||||
enc := json.NewEncoder(output)
|
enc := json.NewEncoder(output)
|
||||||
// prevent > and < from being escaped in the payload
|
// prevent > and < from being escaped in the payload
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22json
|
package spdxjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22json
|
package spdxjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
@ -1,61 +1,66 @@
|
|||||||
{
|
{
|
||||||
|
"spdxVersion": "SPDX-2.3",
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
"SPDXID": "SPDXRef-DOCUMENT",
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
"name": "/some/path",
|
"name": "/some/path",
|
||||||
"spdxVersion": "SPDX-2.2",
|
"documentNamespace": "https://anchore.com/syft/dir/some/path-0f9b165e-1819-43cb-bd58-f61c1c23d6cf",
|
||||||
"creationInfo": {
|
"creationInfo": {
|
||||||
"created": "2022-11-07T14:11:33.934417Z",
|
"licenseListVersion": "3.18",
|
||||||
"creators": [
|
"creators": [
|
||||||
"Organization: Anchore, Inc",
|
"Organization: Anchore, Inc",
|
||||||
"Tool: syft-v0.42.0-bogus"
|
"Tool: syft-v0.42.0-bogus"
|
||||||
],
|
],
|
||||||
"licenseListVersion": "3.18"
|
"created": "2022-11-11T19:24:55Z",
|
||||||
|
"comment": ""
|
||||||
},
|
},
|
||||||
"dataLicense": "CC0-1.0",
|
|
||||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-938e09e5-4fcc-4e3a-8833-a8b59e923bdc",
|
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-1b1d0be59ac59d2c",
|
|
||||||
"name": "package-1",
|
"name": "package-1",
|
||||||
"licenseConcluded": "MIT",
|
"SPDXID": "SPDXRef-Package-python-package-1-1b1d0be59ac59d2c",
|
||||||
|
"versionInfo": "1.0.1",
|
||||||
"downloadLocation": "NOASSERTION",
|
"downloadLocation": "NOASSERTION",
|
||||||
"externalRefs": [
|
|
||||||
{
|
|
||||||
"referenceCategory": "SECURITY",
|
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
|
||||||
"referenceType": "cpe23Type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
|
||||||
"referenceLocator": "a-purl-2",
|
|
||||||
"referenceType": "purl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filesAnalyzed": false,
|
|
||||||
"licenseDeclared": "MIT",
|
|
||||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||||
"versionInfo": "1.0.1"
|
"licenseConcluded": "MIT",
|
||||||
},
|
"licenseDeclared": "MIT",
|
||||||
{
|
"copyrightText": "NOASSERTION",
|
||||||
"SPDXID": "SPDXRef-db4abfe497c180d3",
|
|
||||||
"name": "package-2",
|
|
||||||
"licenseConcluded": "NONE",
|
|
||||||
"downloadLocation": "NOASSERTION",
|
|
||||||
"externalRefs": [
|
"externalRefs": [
|
||||||
{
|
{
|
||||||
"referenceCategory": "SECURITY",
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceType": "cpe23Type",
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
"referenceType": "cpe23Type"
|
"comment": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
"referenceType": "purl",
|
||||||
"referenceType": "purl"
|
"referenceLocator": "a-purl-2",
|
||||||
|
"comment": ""
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"filesAnalyzed": false,
|
},
|
||||||
"licenseDeclared": "NONE",
|
{
|
||||||
|
"name": "package-2",
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||||
|
"versionInfo": "2.0.1",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||||
"versionInfo": "2.0.1"
|
"licenseConcluded": "NONE",
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"copyrightText": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceType": "cpe23Type",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
|
"referenceType": "purl",
|
||||||
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,61 +1,66 @@
|
|||||||
{
|
{
|
||||||
|
"spdxVersion": "SPDX-2.3",
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
"SPDXID": "SPDXRef-DOCUMENT",
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
"name": "user-image-input",
|
"name": "user-image-input",
|
||||||
"spdxVersion": "SPDX-2.2",
|
"documentNamespace": "https://anchore.com/syft/image/user-image-input-5841d063-c3ef-406b-91b4-8a702ef45ce9",
|
||||||
"creationInfo": {
|
"creationInfo": {
|
||||||
"created": "2022-11-07T14:11:33.941498Z",
|
"licenseListVersion": "3.18",
|
||||||
"creators": [
|
"creators": [
|
||||||
"Organization: Anchore, Inc",
|
"Organization: Anchore, Inc",
|
||||||
"Tool: syft-v0.42.0-bogus"
|
"Tool: syft-v0.42.0-bogus"
|
||||||
],
|
],
|
||||||
"licenseListVersion": "3.18"
|
"created": "2022-11-11T19:24:55Z",
|
||||||
|
"comment": ""
|
||||||
},
|
},
|
||||||
"dataLicense": "CC0-1.0",
|
|
||||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-797c013c-1d76-4d3e-9391-521152bfc87d",
|
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-66ba429119b8bec6",
|
|
||||||
"name": "package-1",
|
"name": "package-1",
|
||||||
"licenseConcluded": "MIT",
|
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
|
"versionInfo": "1.0.1",
|
||||||
"downloadLocation": "NOASSERTION",
|
"downloadLocation": "NOASSERTION",
|
||||||
"externalRefs": [
|
|
||||||
{
|
|
||||||
"referenceCategory": "SECURITY",
|
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
|
||||||
"referenceType": "cpe23Type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
|
||||||
"referenceLocator": "a-purl-1",
|
|
||||||
"referenceType": "purl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filesAnalyzed": false,
|
|
||||||
"licenseDeclared": "MIT",
|
|
||||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||||
"versionInfo": "1.0.1"
|
"licenseConcluded": "MIT",
|
||||||
},
|
"licenseDeclared": "MIT",
|
||||||
{
|
"copyrightText": "NOASSERTION",
|
||||||
"SPDXID": "SPDXRef-958443e2d9304af4",
|
|
||||||
"name": "package-2",
|
|
||||||
"licenseConcluded": "NONE",
|
|
||||||
"downloadLocation": "NOASSERTION",
|
|
||||||
"externalRefs": [
|
"externalRefs": [
|
||||||
{
|
{
|
||||||
"referenceCategory": "SECURITY",
|
"referenceCategory": "SECURITY",
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
"referenceType": "cpe23Type",
|
||||||
"referenceType": "cpe23Type"
|
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
"comment": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
"referenceType": "purl",
|
||||||
"referenceType": "purl"
|
"referenceLocator": "a-purl-1",
|
||||||
|
"comment": ""
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"filesAnalyzed": false,
|
},
|
||||||
"licenseDeclared": "NONE",
|
{
|
||||||
|
"name": "package-2",
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||||
|
"versionInfo": "2.0.1",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||||
"versionInfo": "2.0.1"
|
"licenseConcluded": "NONE",
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"copyrightText": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceType": "cpe23Type",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
|
"referenceType": "purl",
|
||||||
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,151 +1,160 @@
|
|||||||
{
|
{
|
||||||
|
"spdxVersion": "SPDX-2.3",
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
"SPDXID": "SPDXRef-DOCUMENT",
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
"name": "user-image-input",
|
"name": "user-image-input",
|
||||||
"spdxVersion": "SPDX-2.2",
|
"documentNamespace": "https://anchore.com/syft/image/user-image-input-8755f340-f205-4bf2-a909-94c623670734",
|
||||||
"creationInfo": {
|
"creationInfo": {
|
||||||
"created": "2022-11-07T14:11:33.947742Z",
|
"licenseListVersion": "3.18",
|
||||||
"creators": [
|
"creators": [
|
||||||
"Organization: Anchore, Inc",
|
"Organization: Anchore, Inc",
|
||||||
"Tool: syft-v0.42.0-bogus"
|
"Tool: syft-v0.42.0-bogus"
|
||||||
],
|
],
|
||||||
"licenseListVersion": "3.18"
|
"created": "2022-11-11T19:24:55Z",
|
||||||
|
"comment": ""
|
||||||
},
|
},
|
||||||
"dataLicense": "CC0-1.0",
|
|
||||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-30117c3a-546f-45b7-a1a8-91f2ecd8d2aa",
|
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-66ba429119b8bec6",
|
|
||||||
"name": "package-1",
|
"name": "package-1",
|
||||||
"licenseConcluded": "MIT",
|
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
|
"versionInfo": "1.0.1",
|
||||||
"downloadLocation": "NOASSERTION",
|
"downloadLocation": "NOASSERTION",
|
||||||
"externalRefs": [
|
|
||||||
{
|
|
||||||
"referenceCategory": "SECURITY",
|
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
|
||||||
"referenceType": "cpe23Type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
|
||||||
"referenceLocator": "a-purl-1",
|
|
||||||
"referenceType": "purl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filesAnalyzed": false,
|
|
||||||
"hasFiles": [
|
|
||||||
"SPDXRef-5265a4dde3edbf7c",
|
|
||||||
"SPDXRef-839d99ee67d9d174",
|
|
||||||
"SPDXRef-9c2f7510199b17f6",
|
|
||||||
"SPDXRef-c641caa71518099f",
|
|
||||||
"SPDXRef-c6f5b29dca12661f",
|
|
||||||
"SPDXRef-f9e49132a4b96ccd"
|
|
||||||
],
|
|
||||||
"licenseDeclared": "MIT",
|
|
||||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||||
"versionInfo": "1.0.1"
|
"licenseConcluded": "MIT",
|
||||||
},
|
"licenseDeclared": "MIT",
|
||||||
{
|
"copyrightText": "NOASSERTION",
|
||||||
"SPDXID": "SPDXRef-958443e2d9304af4",
|
|
||||||
"name": "package-2",
|
|
||||||
"licenseConcluded": "NONE",
|
|
||||||
"downloadLocation": "NOASSERTION",
|
|
||||||
"externalRefs": [
|
"externalRefs": [
|
||||||
{
|
{
|
||||||
"referenceCategory": "SECURITY",
|
"referenceCategory": "SECURITY",
|
||||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
"referenceType": "cpe23Type",
|
||||||
"referenceType": "cpe23Type"
|
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
"comment": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"referenceCategory": "PACKAGE_MANAGER",
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
"referenceType": "purl",
|
||||||
"referenceType": "purl"
|
"referenceLocator": "a-purl-1",
|
||||||
|
"comment": ""
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"filesAnalyzed": false,
|
},
|
||||||
"licenseDeclared": "NONE",
|
{
|
||||||
|
"name": "package-2",
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||||
|
"versionInfo": "2.0.1",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||||
"versionInfo": "2.0.1"
|
"licenseConcluded": "NONE",
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"copyrightText": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceType": "cpe23Type",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
|
"referenceType": "purl",
|
||||||
|
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-9c2f7510199b17f6",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/a1/f6",
|
"fileName": "/a1/f6",
|
||||||
|
"SPDXID": "SPDXRef-9c2f7510199b17f6",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-c6f5b29dca12661f",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/d1/f3",
|
"fileName": "/d1/f3",
|
||||||
|
"SPDXID": "SPDXRef-c6f5b29dca12661f",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-c641caa71518099f",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/d2/f4",
|
"fileName": "/d2/f4",
|
||||||
|
"SPDXID": "SPDXRef-c641caa71518099f",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-5265a4dde3edbf7c",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/f1",
|
"fileName": "/f1",
|
||||||
|
"SPDXID": "SPDXRef-5265a4dde3edbf7c",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-f9e49132a4b96ccd",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/f2",
|
"fileName": "/f2",
|
||||||
|
"SPDXID": "SPDXRef-f9e49132a4b96ccd",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-839d99ee67d9d174",
|
|
||||||
"licenseConcluded": "NOASSERTION",
|
|
||||||
"fileName": "/z1/f5",
|
"fileName": "/z1/f5",
|
||||||
|
"SPDXID": "SPDXRef-839d99ee67d9d174",
|
||||||
"fileTypes": [
|
"fileTypes": [
|
||||||
"OTHER"
|
"OTHER"
|
||||||
]
|
],
|
||||||
|
"checksums": null,
|
||||||
|
"licenseConcluded": "NOASSERTION",
|
||||||
|
"copyrightText": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relationships": [
|
"relationships": [
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c",
|
||||||
"relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd",
|
||||||
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f",
|
||||||
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-c641caa71518099f",
|
||||||
"relatedSpdxElement": "SPDXRef-c641caa71518099f"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174",
|
||||||
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f"
|
"relationshipType": "CONTAINS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spdxElementId": "SPDXRef-66ba429119b8bec6",
|
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||||
"relationshipType": "CONTAINS",
|
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6",
|
||||||
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd"
|
"relationshipType": "CONTAINS"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
package spdx22json
|
package spdxjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22tagvalue
|
package spdxtagvalue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||||
doc, err := tvloader.Load2_2(reader)
|
doc, err := tvloader.Load2_3(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||||
}
|
}
|
||||||
15
syft/formats/spdxtagvalue/encoder.go
Normal file
15
syft/formats/spdxtagvalue/encoder.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package spdxtagvalue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/spdx/tools-golang/tvsaver"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||||
|
model := spdxhelpers.ToFormatModel(s)
|
||||||
|
return tvsaver.Save2_3(model, output)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22tagvalue
|
package spdxtagvalue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package spdx22tagvalue
|
package spdxtagvalue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
@ -1,12 +1,12 @@
|
|||||||
SPDXVersion: SPDX-2.2
|
SPDXVersion: SPDX-2.3
|
||||||
DataLicense: CC0-1.0
|
DataLicense: CC0-1.0
|
||||||
SPDXID: SPDXRef-DOCUMENT
|
SPDXID: SPDXRef-DOCUMENT
|
||||||
DocumentName: .
|
DocumentName: .
|
||||||
DocumentNamespace: https://anchore.com/syft/dir/a4820ad7-d106-497f-bda7-e694e9ad1050
|
DocumentNamespace: https://anchore.com/syft/dir/b51d2446-85b4-4b22-9762-12fc135730a7
|
||||||
LicenseListVersion: 3.18
|
LicenseListVersion: 3.18
|
||||||
Creator: Organization: Anchore, Inc
|
Creator: Organization: Anchore, Inc
|
||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: 2022-11-07T14:11:42Z
|
Created: 2022-11-11T19:25:16Z
|
||||||
|
|
||||||
##### Package: @at-sign
|
##### Package: @at-sign
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ PackageName: @at-sign
|
|||||||
SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
PackageLicenseConcluded: NONE
|
PackageLicenseConcluded: NONE
|
||||||
PackageLicenseDeclared: NONE
|
PackageLicenseDeclared: NONE
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
@ -24,6 +25,7 @@ PackageName: some/slashes
|
|||||||
SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
PackageLicenseConcluded: NONE
|
PackageLicenseConcluded: NONE
|
||||||
PackageLicenseDeclared: NONE
|
PackageLicenseDeclared: NONE
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
@ -34,6 +36,7 @@ PackageName: under_scores
|
|||||||
SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from the following paths:
|
||||||
PackageLicenseConcluded: NONE
|
PackageLicenseConcluded: NONE
|
||||||
PackageLicenseDeclared: NONE
|
PackageLicenseDeclared: NONE
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
@ -1,12 +1,12 @@
|
|||||||
SPDXVersion: SPDX-2.2
|
SPDXVersion: SPDX-2.3
|
||||||
DataLicense: CC0-1.0
|
DataLicense: CC0-1.0
|
||||||
SPDXID: SPDXRef-DOCUMENT
|
SPDXID: SPDXRef-DOCUMENT
|
||||||
DocumentName: /some/path
|
DocumentName: /some/path
|
||||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-01844fe2-60ea-45fd-bb92-df9f5660330d
|
DocumentNamespace: https://anchore.com/syft/dir/some/path-94301cf0-21fd-481a-b555-ea767674cc93
|
||||||
LicenseListVersion: 3.18
|
LicenseListVersion: 3.18
|
||||||
Creator: Organization: Anchore, Inc
|
Creator: Organization: Anchore, Inc
|
||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: 2022-11-07T14:11:42Z
|
Created: 2022-11-11T19:25:16Z
|
||||||
|
|
||||||
##### Package: package-2
|
##### Package: package-2
|
||||||
|
|
||||||
@ -15,11 +15,12 @@ SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
|||||||
PackageVersion: 2.0.1
|
PackageVersion: 2.0.1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1
|
||||||
PackageLicenseConcluded: NONE
|
PackageLicenseConcluded: NONE
|
||||||
PackageLicenseDeclared: NONE
|
PackageLicenseDeclared: NONE
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||||
ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||||
|
|
||||||
##### Package: package-1
|
##### Package: package-1
|
||||||
|
|
||||||
@ -28,9 +29,10 @@ SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
|
|||||||
PackageVersion: 1.0.1
|
PackageVersion: 1.0.1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1
|
||||||
PackageLicenseConcluded: MIT
|
PackageLicenseConcluded: MIT
|
||||||
PackageLicenseDeclared: MIT
|
PackageLicenseDeclared: MIT
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
||||||
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
SPDXVersion: SPDX-2.2
|
SPDXVersion: SPDX-2.3
|
||||||
DataLicense: CC0-1.0
|
DataLicense: CC0-1.0
|
||||||
SPDXID: SPDXRef-DOCUMENT
|
SPDXID: SPDXRef-DOCUMENT
|
||||||
DocumentName: user-image-input
|
DocumentName: user-image-input
|
||||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-32ec506e-82d8-4da6-991d-e1000e4e562e
|
DocumentNamespace: https://anchore.com/syft/image/user-image-input-258730be-7925-4ef3-9009-d9dc532d2fec
|
||||||
LicenseListVersion: 3.18
|
LicenseListVersion: 3.18
|
||||||
Creator: Organization: Anchore, Inc
|
Creator: Organization: Anchore, Inc
|
||||||
Creator: Tool: syft-v0.42.0-bogus
|
Creator: Tool: syft-v0.42.0-bogus
|
||||||
Created: 2022-11-07T14:11:42Z
|
Created: 2022-11-11T19:25:16Z
|
||||||
|
|
||||||
##### Package: package-2
|
##### Package: package-2
|
||||||
|
|
||||||
@ -15,11 +15,12 @@ SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
|||||||
PackageVersion: 2.0.1
|
PackageVersion: 2.0.1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||||
PackageLicenseConcluded: NONE
|
PackageLicenseConcluded: NONE
|
||||||
PackageLicenseDeclared: NONE
|
PackageLicenseDeclared: NONE
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||||
ExternalRef: PACKAGE_MANAGER purl pkg:deb/debian/package-2@2.0.1
|
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||||
|
|
||||||
##### Package: package-1
|
##### Package: package-1
|
||||||
|
|
||||||
@ -28,9 +29,10 @@ SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
|||||||
PackageVersion: 1.0.1
|
PackageVersion: 1.0.1
|
||||||
PackageDownloadLocation: NOASSERTION
|
PackageDownloadLocation: NOASSERTION
|
||||||
FilesAnalyzed: false
|
FilesAnalyzed: false
|
||||||
|
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||||
PackageLicenseConcluded: MIT
|
PackageLicenseConcluded: MIT
|
||||||
PackageLicenseDeclared: MIT
|
PackageLicenseDeclared: MIT
|
||||||
PackageCopyrightText: NOASSERTION
|
PackageCopyrightText: NOASSERTION
|
||||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||||
ExternalRef: PACKAGE_MANAGER purl a-purl-1
|
ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||||
|
|
||||||
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
package spdx22tagvalue
|
package spdxtagvalue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22json"
|
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||||
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
"github.com/anchore/syft/syft/formats/table"
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
@ -23,8 +23,8 @@ import (
|
|||||||
|
|
||||||
var convertibleFormats = []sbom.Format{
|
var convertibleFormats = []sbom.Format{
|
||||||
syftjson.Format(),
|
syftjson.Format(),
|
||||||
spdx22json.Format(),
|
spdxjson.Format(),
|
||||||
spdx22tagvalue.Format(),
|
spdxtagvalue.Format(),
|
||||||
cyclonedxjson.Format(),
|
cyclonedxjson.Format(),
|
||||||
cyclonedxxml.Format(),
|
cyclonedxxml.Format(),
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user