diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 446287e9dec..befd6aad028 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -40,7 +40,7 @@ import ( "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/event" "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/sbom" "github.com/anchore/syft/syft/source" @@ -49,7 +49,7 @@ import ( var ( allowedAttestFormats = []sbom.FormatID{ syftjson.ID, - spdx22json.ID, + spdxjson.ID, cyclonedxjson.ID, } @@ -356,7 +356,7 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam func formatPredicateType(format sbom.Format) string { switch format.ID() { - case spdx22json.ID: + case spdxjson.ID: return in_toto.PredicateSPDX case cyclonedxjson.ID: return in_toto.PredicateCycloneDX diff --git a/cmd/syft/cli/options/format.go b/cmd/syft/cli/options/format.go index 602b71e382d..e8fabb91432 100644 --- a/cmd/syft/cli/options/format.go +++ b/cmd/syft/cli/options/format.go @@ -4,8 +4,8 @@ import ( "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdx22json" - "github.com/anchore/syft/syft/formats/spdx22tagvalue" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/formats/text" @@ -21,9 +21,9 @@ func FormatAliases(ids ...sbom.FormatID) (aliases []string) { aliases = append(aliases, "text") case table.ID: aliases = append(aliases, "table") - case spdx22json.ID: + case spdxjson.ID: aliases = append(aliases, "spdx-json") - case spdx22tagvalue.ID: + case spdxtagvalue.ID: aliases = append(aliases, "spdx-tag-value") case cyclonedxxml.ID: aliases = append(aliases, "cyclonedx-xml") diff --git a/go.mod b/go.mod index 9dfb3f6e7fc..da9890ad762 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.2.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/cobra v1.6.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 11df57a6b47..6bff6b8a328 100644 --- a/go.sum +++ b/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/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/tools-golang v0.2.0 h1:KBNcw7xvVycRWeCWZK/5xQJA+plymW1+rTCs8ekJDro= -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 h1:6uvaOTv4GeRqQV6O1/znbpziqhctMRLTy3OGeZrNMic= +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.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= diff --git a/syft/formats.go b/syft/formats.go index 37b3b0bd73b..e3cd5d20862 100644 --- a/syft/formats.go +++ b/syft/formats.go @@ -5,8 +5,8 @@ import ( "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdx22json" - "github.com/anchore/syft/syft/formats/spdx22tagvalue" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/formats/template" @@ -23,8 +23,8 @@ const ( CycloneDxXMLFormatID = cyclonedxxml.ID CycloneDxJSONFormatID = cyclonedxjson.ID GitHubFormatID = github.ID - SPDXTagValueFormatID = spdx22tagvalue.ID - SPDXJSONFormatID = spdx22json.ID + SPDXTagValueFormatID = spdxtagvalue.ID + SPDXJSONFormatID = spdxjson.ID TemplateFormatID = template.ID ) diff --git a/syft/formats/common/spdxhelpers/document_namespace.go b/syft/formats/common/spdxhelpers/document_namespace.go index 584713bcbf6..f4ad43f8086 100644 --- a/syft/formats/common/spdxhelpers/document_namespace.go +++ b/syft/formats/common/spdxhelpers/document_namespace.go @@ -11,6 +11,12 @@ import ( "github.com/anchore/syft/syft/source" ) +const ( + inputImage = "image" + inputDirectory = "dir" + inputFile = "file" +) + func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string) { name := DocumentName(srcMetadata) return name, DocumentNamespace(name, srcMetadata) @@ -20,11 +26,11 @@ func DocumentNamespace(name string, srcMetadata source.Metadata) string { input := "unknown-source-type" switch srcMetadata.Scheme { case source.ImageScheme: - input = "image" + input = inputImage case source.DirectoryScheme: - input = "dir" + input = inputDirectory case source.FileScheme: - input = "file" + input = inputFile } uniqueID := uuid.Must(uuid.NewRandom()) diff --git a/syft/formats/common/spdxhelpers/external_ref.go b/syft/formats/common/spdxhelpers/external_ref.go index 98a1bc37ece..df7415166cf 100644 --- a/syft/formats/common/spdxhelpers/external_ref.go +++ b/syft/formats/common/spdxhelpers/external_ref.go @@ -4,7 +4,7 @@ type ReferenceCategory string const ( SecurityReferenceCategory ReferenceCategory = "SECURITY" - PackageManagerReferenceCategory ReferenceCategory = "PACKAGE_MANAGER" + PackageManagerReferenceCategory ReferenceCategory = "PACKAGE-MANAGER" OtherReferenceCategory ReferenceCategory = "OTHER" ) diff --git a/syft/formats/common/spdxhelpers/originator_test.go b/syft/formats/common/spdxhelpers/originator_test.go index a35c707fbf0..3dc4e0208d4 100644 --- a/syft/formats/common/spdxhelpers/originator_test.go +++ b/syft/formats/common/spdxhelpers/originator_test.go @@ -109,7 +109,11 @@ func Test_Originator(t *testing.T) { } for _, test := range tests { 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) }) } } diff --git a/syft/formats/common/spdxhelpers/origintor.go b/syft/formats/common/spdxhelpers/origintor.go index dd718cbf4c0..7a6c14bf112 100644 --- a/syft/formats/common/spdxhelpers/origintor.go +++ b/syft/formats/common/spdxhelpers/origintor.go @@ -9,9 +9,11 @@ import ( // Originator needs to conform to the SPDX spec here: // https://spdx.github.io/spdx-spec/package-information/#76-package-originator-field // Available options are: , NOASSERTION, Person: , Organization: -func Originator(p pkg.Package) string { +// return values are: , +func Originator(p pkg.Package) (string, string) { + typ := "" + author := "" if hasMetadata(p) { - author := "" switch metadata := p.Metadata.(type) { case pkg.ApkMetadata: author = metadata.Maintainer @@ -29,13 +31,14 @@ func Originator(p pkg.Package) string { author = metadata.Authors[0] } case pkg.RpmMetadata: - return "Organization: " + metadata.Vendor + typ = "Organization" + author = metadata.Vendor case pkg.DpkgMetadata: author = metadata.Maintainer } - if author != "" { - return "Person: " + author + if typ == "" && author != "" { + typ = "Person" } } - return "" + return typ, author } diff --git a/syft/formats/common/spdxhelpers/to_format_model.go b/syft/formats/common/spdxhelpers/to_format_model.go new file mode 100644 index 00000000000..1ac0a1f4264 --- /dev/null +++ b/syft/formats/common/spdxhelpers/to_format_model.go @@ -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 +} diff --git a/syft/formats/spdx22json/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go similarity index 70% rename from syft/formats/spdx22json/to_format_model_test.go rename to syft/formats/common/spdxhelpers/to_format_model_test.go index 254274a386c..aeeb2a600e5 100644 --- a/syft/formats/spdx22json/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -1,16 +1,16 @@ -package spdx22json +package spdxhelpers import ( "fmt" "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/require" "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/spdx22json/model" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) @@ -28,7 +28,7 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "application/vnd.unknown", }, expected: []string{ - string(spdxhelpers.ApplicationFileType), + string(ApplicationFileType), }, }, { @@ -37,8 +37,8 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "application/zip", }, expected: []string{ - string(spdxhelpers.ApplicationFileType), - string(spdxhelpers.ArchiveFileType), + string(ApplicationFileType), + string(ArchiveFileType), }, }, { @@ -47,7 +47,7 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "audio/ogg", }, expected: []string{ - string(spdxhelpers.AudioFileType), + string(AudioFileType), }, }, { @@ -56,7 +56,7 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "video/3gpp", }, expected: []string{ - string(spdxhelpers.VideoFileType), + string(VideoFileType), }, }, { @@ -65,7 +65,7 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "text/html", }, expected: []string{ - string(spdxhelpers.TextFileType), + string(TextFileType), }, }, { @@ -74,7 +74,7 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "image/png", }, expected: []string{ - string(spdxhelpers.ImageFileType), + string(ImageFileType), }, }, { @@ -83,8 +83,8 @@ func Test_toFileTypes(t *testing.T) { MIMEType: "application/x-sharedlib", }, expected: []string{ - string(spdxhelpers.ApplicationFileType), - string(spdxhelpers.BinaryFileType), + string(ApplicationFileType), + string(BinaryFileType), }, }, } @@ -100,18 +100,18 @@ func Test_lookupRelationship(t *testing.T) { tests := []struct { input artifact.RelationshipType exists bool - ty spdxhelpers.RelationshipType + ty RelationshipType comment string }{ { input: artifact.ContainsRelationship, exists: true, - ty: spdxhelpers.ContainsRelationship, + ty: ContainsRelationship, }, { input: artifact.OwnershipByFileOverlapRelationship, 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", }, { @@ -133,7 +133,7 @@ func Test_toFileChecksums(t *testing.T) { tests := []struct { name string digests []file.Digest - expected []model.Checksum + expected []common.Checksum }{ { name: "empty", @@ -150,14 +150,14 @@ func Test_toFileChecksums(t *testing.T) { Value: "meh", }, }, - expected: []model.Checksum{ + expected: []common.Checksum{ { - Algorithm: "SHA256", - ChecksumValue: "deadbeefcafe", + Algorithm: "SHA256", + Value: "deadbeefcafe", }, { - Algorithm: "MD5", - ChecksumValue: "meh", + Algorithm: "MD5", + Value: "meh", }, }, }, @@ -170,7 +170,6 @@ func Test_toFileChecksums(t *testing.T) { } func Test_fileIDsForPackage(t *testing.T) { - p := pkg.Package{ Name: "bogus", } @@ -180,15 +179,19 @@ func Test_fileIDsForPackage(t *testing.T) { FileSystemID: "nowhere", } + docElementId := func(identifiable artifact.Identifiable) common.DocElementID { + return common.DocElementID{ + ElementRefID: toSPDXID(identifiable), + } + } + tests := []struct { name string - id string relationships []artifact.Relationship - expected []string + expected []*spdx.Relationship }{ { - name: "find file IDs for packages with package-file relationships", - id: model.ElementID(p.ID()).String(), + name: "package-to-file contains relationships", relationships: []artifact.Relationship{ { From: p, @@ -196,13 +199,16 @@ func Test_fileIDsForPackage(t *testing.T) { Type: artifact.ContainsRelationship, }, }, - expected: []string{ - model.ElementID(c.ID()).String(), + expected: []*spdx.Relationship{ + { + Relationship: "CONTAINS", + RefA: docElementId(p), + RefB: docElementId(c), + }, }, }, { - name: "ignore package-to-package", - id: model.ElementID(p.ID()).String(), + name: "package-to-package", relationships: []artifact.Relationship{ { From: p, @@ -210,11 +216,16 @@ func Test_fileIDsForPackage(t *testing.T) { Type: artifact.ContainsRelationship, }, }, - expected: []string{}, + expected: []*spdx.Relationship{ + { + Relationship: "CONTAINS", + RefA: docElementId(p), + RefB: docElementId(p), + }, + }, }, { name: "ignore file-to-file", - id: model.ElementID(p.ID()).String(), relationships: []artifact.Relationship{ { From: c, @@ -222,11 +233,10 @@ func Test_fileIDsForPackage(t *testing.T) { Type: artifact.ContainsRelationship, }, }, - expected: []string{}, + expected: nil, }, { name: "ignore file-to-package", - id: model.ElementID(p.ID()).String(), relationships: []artifact.Relationship{ { From: c, @@ -234,11 +244,10 @@ func Test_fileIDsForPackage(t *testing.T) { Type: artifact.ContainsRelationship, }, }, - expected: []string{}, + expected: nil, }, { - name: "filter by relationship type", - id: model.ElementID(p.ID()).String(), + name: "include package-to-file overlap relationships", relationships: []artifact.Relationship{ { From: p, @@ -246,12 +255,20 @@ func Test_fileIDsForPackage(t *testing.T) { 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 { 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 { t.Run(test.name, func(t *testing.T) { catalog := pkg.NewCatalog(test.pkg) - pkgs := toPackages(catalog, nil) + pkgs := toPackages(catalog) require.Len(t, pkgs, 1) - p := pkgs[0] - if test.expectedDigest == "" { - require.Len(t, p.Checksums, 0) - } else { - require.Len(t, p.Checksums, 1) - c := p.Checksums[0] - require.Equal(t, test.expectedDigest, fmt.Sprintf("%s:%s", c.Algorithm, c.ChecksumValue)) + 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)) + } + } } }) } diff --git a/syft/formats/common/spdxhelpers/to_syft_model.go b/syft/formats/common/spdxhelpers/to_syft_model.go index cfc3ef729dc..1ff29288f80 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model.go +++ b/syft/formats/common/spdxhelpers/to_syft_model.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/spdx/tools-golang/spdx" + spdx "github.com/spdx/tools-golang/spdx/v2_3" "github.com/anchore/packageurl-go" "github.com/anchore/syft/internal/log" @@ -19,7 +19,7 @@ import ( "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 { 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{}) src := source.Metadata{Scheme: source.UnknownScheme} - if doc.CreationInfo != nil { - src.Scheme = extractSchemeFromNamespace(doc.CreationInfo.DocumentNamespace) - } + src.Scheme = extractSchemeFromNamespace(doc.DocumentNamespace) s := &sbom.SBOM{ Source: src, @@ -63,18 +61,18 @@ func extractSchemeFromNamespace(ns string) source.Scheme { parts := strings.Split(u.Path, "/") for _, p := range parts { switch p { - case "file": + case inputFile: return source.FileScheme - case "image": + case inputImage: return source.ImageScheme - case "dir": + case inputDirectory: return source.DirectoryScheme } } return source.UnknownScheme } -func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release { +func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release { for _, p := range doc.Packages { purlValue := findPURLValue(p) if purlValue == "" { @@ -107,7 +105,7 @@ func findLinuxReleaseByPURL(doc *spdx.Document2_2) *linux.Release { 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 { syftPkg := toSyftPackage(p) 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) { - for _, f := range doc.UnpackagedFiles { +func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *spdx.Document) { + for _, f := range doc.Files { l := toSyftLocation(f) 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) { - for _, digest := range f.FileChecksums { +func toFileDigests(f *spdx.File) (digests []file.Digest) { + for _, digest := range f.Checksums { digests = append(digests, file.Digest{ Algorithm: string(digest.Algorithm), Value: digest.Value, @@ -135,9 +133,9 @@ func toFileDigests(f *spdx.File2_2) (digests []file.Digest) { 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 - for _, typ := range f.FileType { + for _, typ := range f.FileTypes { switch FileType(typ) { case ImageFileType: meta.MIMEType = "image/" @@ -157,11 +155,11 @@ func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) { 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 for _, r := range doc.Relationships { // 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) continue } @@ -205,7 +203,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2 return out } -func toSyftCoordinates(f *spdx.File2_2) source.Coordinates { +func toSyftCoordinates(f *spdx.File) source.Coordinates { const layerIDPrefix = "layerID: " var fileSystemID string 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{ Coordinates: toSyftCoordinates(f), VirtualPath: f.FileName, @@ -255,7 +253,7 @@ func findQualifierValue(purl packageurl.PackageURL, qualifier string) string { return "" } -func extractPkgInfo(p *spdx.Package2_2) pkgInfo { +func extractPkgInfo(p *spdx.Package) pkgInfo { pu := findPURLValue(p) purl, err := packageurl.FromString(pu) 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) metadataType, metadata := extractMetadata(p, info) sP := pkg.Package{ @@ -289,7 +287,7 @@ func toSyftPackage(p *spdx.Package2_2) *pkg.Package { } //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) upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream) upstream := strings.SplitN(upstreamValue, "@", 2) @@ -298,12 +296,20 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf if len(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 { case pkg.ApkPkg: return pkg.ApkMetadataType, pkg.ApkMetadata{ Package: p.PackageName, OriginPackage: upstreamName, - Maintainer: p.PackageSupplierPerson, + Maintainer: supplier, Version: p.PackageVersion, License: p.PackageLicenseDeclared, Architecture: arch, @@ -329,7 +335,7 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf Arch: arch, SourceRpm: upstreamValue, License: license, - Vendor: p.PackageOriginatorOrganization, + Vendor: originator, } case pkg.DebPkg: return pkg.DpkgMetadataType, pkg.DpkgMetadata{ @@ -338,12 +344,12 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf Version: p.PackageVersion, SourceVersion: upstreamVersion, Architecture: arch, - Maintainer: p.PackageOriginatorPerson, + Maintainer: originator, } case pkg.JavaPkg: var digests []file.Digest - for algorithm, value := range p.PackageChecksums { - digests = append(digests, file.Digest{Algorithm: string(algorithm), Value: value.Value}) + for _, value := range p.PackageChecksums { + digests = append(digests, file.Digest{Algorithm: string(value.Algorithm), Value: value.Value}) } return pkg.JavaMetadataType, pkg.JavaMetadata{ ArchiveDigests: digests, @@ -366,7 +372,7 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf return pkg.UnknownMetadataType, nil } -func findPURLValue(p *spdx.Package2_2) string { +func findPURLValue(p *spdx.Package) string { for _, r := range p.PackageExternalReferences { if r.RefType == string(PurlExternalRefType) { return r.Locator @@ -375,7 +381,7 @@ func findPURLValue(p *spdx.Package2_2) string { return "" } -func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) { +func extractCPEs(p *spdx.Package) (cpes []pkg.CPE) { for _, r := range p.PackageExternalReferences { if r.RefType == string(Cpe23ExternalRefType) { cpe, err := pkg.NewCPE(r.Locator) diff --git a/syft/formats/common/spdxhelpers/to_syft_model_test.go b/syft/formats/common/spdxhelpers/to_syft_model_test.go index 4ca8bb61b8a..3f0b4861c35 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model_test.go +++ b/syft/formats/common/spdxhelpers/to_syft_model_test.go @@ -3,7 +3,8 @@ package spdxhelpers import ( "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/require" @@ -12,32 +13,27 @@ import ( ) func TestToSyftModel(t *testing.T) { - sbom, err := ToSyftModel(&spdx.Document2_2{ - CreationInfo: &spdx.CreationInfo2_2{ - SPDXVersion: "1", - DataLicense: "GPL", - SPDXIdentifier: "id-doc-1", - DocumentName: "docName", - DocumentNamespace: "docNamespace", - ExternalDocumentReferences: nil, - LicenseListVersion: "", - CreatorPersons: nil, - CreatorOrganizations: nil, - CreatorTools: nil, - Created: "", - CreatorComment: "", - DocumentComment: "", + sbom, err := ToSyftModel(&spdx.Document{ + SPDXVersion: "1", + DataLicense: "GPL", + SPDXIdentifier: "id-doc-1", + DocumentName: "docName", + DocumentNamespace: "docNamespace", + ExternalDocumentReferences: nil, + DocumentComment: "", + CreationInfo: &spdx.CreationInfo{ + LicenseListVersion: "", + Created: "", + CreatorComment: "", }, - Packages: map[spdx.ElementID]*spdx.Package2_2{ - "id-pkg-1": { - PackageName: "pkg-1", - PackageSPDXIdentifier: "id-pkg-1", - PackageVersion: "5.4.3", - PackageSupplierPerson: "", - PackageSupplierOrganization: "", - PackageLicenseDeclared: "", - PackageDescription: "", - PackageExternalReferences: []*spdx.PackageExternalReference2_2{ + Packages: []*spdx.Package{ + { + PackageName: "pkg-1", + PackageSPDXIdentifier: "id-pkg-1", + PackageVersion: "5.4.3", + PackageLicenseDeclared: "", + PackageDescription: "", + PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: "SECURITY", Locator: "cpe:2.3:a:pkg-1:pkg-1:5.4.3:*:*:*:*:*:*:*", @@ -49,22 +45,20 @@ func TestToSyftModel(t *testing.T) { 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", RefType: "purl", }, }, Files: nil, }, - "id-pkg-2": { - PackageName: "pkg-2", - PackageSPDXIdentifier: "id-pkg-2", - PackageVersion: "7.3.1", - PackageSupplierPerson: "", - PackageSupplierOrganization: "", - PackageLicenseDeclared: "", - PackageDescription: "", - PackageExternalReferences: []*spdx.PackageExternalReference2_2{ + { + PackageName: "pkg-2", + PackageSPDXIdentifier: "id-pkg-2", + PackageVersion: "7.3.1", + PackageLicenseDeclared: "", + PackageDescription: "", + PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: "SECURITY", Locator: "cpe:2.3:a:pkg-2:pkg-2:7.3.1:*:*:*:*:*:*:*", @@ -81,7 +75,7 @@ func TestToSyftModel(t *testing.T) { 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", RefType: "purl", }, @@ -89,8 +83,7 @@ func TestToSyftModel(t *testing.T) { Files: nil, }, }, - UnpackagedFiles: map[spdx.ElementID]*spdx.File2_2{}, - Relationships: []*spdx.Relationship2_2{}, + Relationships: []*spdx.Relationship{}, }) assert.NoError(t, err) @@ -120,17 +113,17 @@ func TestToSyftModel(t *testing.T) { func Test_extractMetadata(t *testing.T) { oneTwoThreeFour := 1234 tests := []struct { - pkg spdx.Package2_2 + pkg spdx.Package metaType pkg.MetadataType meta interface{} }{ { - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "SomeDebPkg", 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", RefType: "purl", }, @@ -146,12 +139,12 @@ func Test_extractMetadata(t *testing.T) { }, }, { - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "SomeApkPkg", 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", RefType: "purl", }, @@ -166,12 +159,12 @@ func Test_extractMetadata(t *testing.T) { }, }, { - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "SomeRpmPkg", 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", RefType: "purl", }, @@ -238,24 +231,24 @@ func TestExtractSourceFromNamespaces(t *testing.T) { func TestH1Digest(t *testing.T) { tests := []struct { name string - pkg spdx.Package2_2 + pkg spdx.Package expectedDigest string }{ { name: "valid h1digest", - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "github.com/googleapis/gnostic", 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", RefType: "purl", }, }, - PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{ - spdx.SHA256: { - Algorithm: spdx.SHA256, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA256, Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c", }, }, @@ -264,19 +257,19 @@ func TestH1Digest(t *testing.T) { }, { name: "invalid h1digest algorithm", - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "github.com/googleapis/gnostic", 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", RefType: "purl", }, }, - PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{ - spdx.SHA256: { - Algorithm: spdx.SHA1, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA1, Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c", }, }, @@ -285,19 +278,19 @@ func TestH1Digest(t *testing.T) { }, { name: "invalid h1digest digest", - pkg: spdx.Package2_2{ + pkg: spdx.Package{ PackageName: "github.com/googleapis/gnostic", 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", RefType: "purl", }, }, - PackageChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{ - spdx.SHA256: { - Algorithm: spdx.SHA256, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA256, Value: "", }, }, diff --git a/syft/formats/formats.go b/syft/formats/formats.go index f755f02c7ab..32a53795719 100644 --- a/syft/formats/formats.go +++ b/syft/formats/formats.go @@ -9,8 +9,8 @@ import ( "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdx22json" - "github.com/anchore/syft/syft/formats/spdx22tagvalue" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/formats/template" @@ -24,8 +24,8 @@ func Formats() []sbom.Format { cyclonedxxml.Format(), cyclonedxjson.Format(), github.Format(), - spdx22tagvalue.Format(), - spdx22json.Format(), + spdxtagvalue.Format(), + spdxjson.Format(), table.Format(), text.Format(), template.Format(), @@ -61,9 +61,9 @@ func ByName(name string) sbom.Format { case "github", "githubjson": return ByID(github.ID) case "spdx", "spdxtv", "spdxtagvalue": - return ByID(spdx22tagvalue.ID) + return ByID(spdxtagvalue.ID) case "spdxjson": - return ByID(spdx22json.ID) + return ByID(spdxjson.ID) case "table": return ByID(table.ID) case "text": diff --git a/syft/formats/formats_test.go b/syft/formats/formats_test.go index 86211f12427..c2b847ae833 100644 --- a/syft/formats/formats_test.go +++ b/syft/formats/formats_test.go @@ -12,8 +12,8 @@ import ( "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdx22json" - "github.com/anchore/syft/syft/formats/spdx22tagvalue" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/formats/template" @@ -78,41 +78,41 @@ func TestByName(t *testing.T) { // SPDX Tag-Value { name: "spdx", - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdx-tag-value", - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdx-tv", - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdxtv", // clean variant - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdx-2-tag-value", // clean variant - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdx-2-tagvalue", // clean variant - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, { name: "spdx2-tagvalue", // clean variant - want: spdx22tagvalue.ID, + want: spdxtagvalue.ID, }, // SPDX JSON { name: "spdx-json", - want: spdx22json.ID, + want: spdxjson.ID, }, { name: "spdx-2-json", - want: spdx22json.ID, + want: spdxjson.ID, }, // Cyclonedx JSON diff --git a/syft/formats/spdx22json/decoder.go b/syft/formats/spdx22json/decoder.go deleted file mode 100644 index 1e1bbb97817..00000000000 --- a/syft/formats/spdx22json/decoder.go +++ /dev/null @@ -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) -} diff --git a/syft/formats/spdx22json/model/annotation.go b/syft/formats/spdx22json/model/annotation.go deleted file mode 100644 index e924cb7c678..00000000000 --- a/syft/formats/spdx22json/model/annotation.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/checksum.go b/syft/formats/spdx22json/model/checksum.go deleted file mode 100644 index b995a95fb9f..00000000000 --- a/syft/formats/spdx22json/model/checksum.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/creation_info.go b/syft/formats/spdx22json/model/creation_info.go deleted file mode 100644 index c7b545d98e1..00000000000 --- a/syft/formats/spdx22json/model/creation_info.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/document.go b/syft/formats/spdx22json/model/document.go deleted file mode 100644 index 7675ff42635..00000000000 --- a/syft/formats/spdx22json/model/document.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/element.go b/syft/formats/spdx22json/model/element.go deleted file mode 100644 index 75d250442f2..00000000000 --- a/syft/formats/spdx22json/model/element.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/element_id.go b/syft/formats/spdx22json/model/element_id.go deleted file mode 100644 index 45f65da097e..00000000000 --- a/syft/formats/spdx22json/model/element_id.go +++ /dev/null @@ -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)) -} diff --git a/syft/formats/spdx22json/model/external_document_ref.go b/syft/formats/spdx22json/model/external_document_ref.go deleted file mode 100644 index 10e1d9ec3b1..00000000000 --- a/syft/formats/spdx22json/model/external_document_ref.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/file.go b/syft/formats/spdx22json/model/file.go deleted file mode 100644 index cc039facb4a..00000000000 --- a/syft/formats/spdx22json/model/file.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/has_extracted_licensing_info.go b/syft/formats/spdx22json/model/has_extracted_licensing_info.go deleted file mode 100644 index 8c0748073b3..00000000000 --- a/syft/formats/spdx22json/model/has_extracted_licensing_info.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/item.go b/syft/formats/spdx22json/model/item.go deleted file mode 100644 index 178fbf8421e..00000000000 --- a/syft/formats/spdx22json/model/item.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/package.go b/syft/formats/spdx22json/model/package.go deleted file mode 100644 index 711c859412f..00000000000 --- a/syft/formats/spdx22json/model/package.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/package_verification_code.go b/syft/formats/spdx22json/model/package_verification_code.go deleted file mode 100644 index 508c9169ed0..00000000000 --- a/syft/formats/spdx22json/model/package_verification_code.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/relationship.go b/syft/formats/spdx22json/model/relationship.go deleted file mode 100644 index 272246302f6..00000000000 --- a/syft/formats/spdx22json/model/relationship.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/snippet.go b/syft/formats/spdx22json/model/snippet.go deleted file mode 100644 index 0d39e5dca39..00000000000 --- a/syft/formats/spdx22json/model/snippet.go +++ /dev/null @@ -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"` -} diff --git a/syft/formats/spdx22json/model/version.go b/syft/formats/spdx22json/model/version.go deleted file mode 100644 index 8f105cbfa71..00000000000 --- a/syft/formats/spdx22json/model/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package model - -const Version = "SPDX-2.2" diff --git a/syft/formats/spdx22json/to_format_model.go b/syft/formats/spdx22json/to_format_model.go deleted file mode 100644 index 44a865a4969..00000000000 --- a/syft/formats/spdx22json/to_format_model.go +++ /dev/null @@ -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, "", "" -} diff --git a/syft/formats/spdx22tagvalue/encoder.go b/syft/formats/spdx22tagvalue/encoder.go deleted file mode 100644 index c195391c273..00000000000 --- a/syft/formats/spdx22tagvalue/encoder.go +++ /dev/null @@ -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) -} diff --git a/syft/formats/spdx22tagvalue/to_format_model.go b/syft/formats/spdx22tagvalue/to_format_model.go deleted file mode 100644 index d9ed94a9377..00000000000 --- a/syft/formats/spdx22tagvalue/to_format_model.go +++ /dev/null @@ -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 -} diff --git a/syft/formats/spdx22tagvalue/to_format_model_test.go b/syft/formats/spdx22tagvalue/to_format_model_test.go deleted file mode 100644 index 3b302b8643d..00000000000 --- a/syft/formats/spdx22tagvalue/to_format_model_test.go +++ /dev/null @@ -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)) - } - } - } - }) - } -} diff --git a/syft/formats/spdxjson/decoder.go b/syft/formats/spdxjson/decoder.go new file mode 100644 index 00000000000..a69c0e07d2e --- /dev/null +++ b/syft/formats/spdxjson/decoder.go @@ -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) +} diff --git a/syft/formats/spdx22json/decoder_test.go b/syft/formats/spdxjson/decoder_test.go similarity index 93% rename from syft/formats/spdx22json/decoder_test.go rename to syft/formats/spdxjson/decoder_test.go index 004b14d76e7..574fb0ba2d9 100644 --- a/syft/formats/spdx22json/decoder_test.go +++ b/syft/formats/spdxjson/decoder_test.go @@ -1,4 +1,4 @@ -package spdx22json +package spdxjson import ( "fmt" @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/pkg" ) @@ -60,15 +61,15 @@ func TestSPDXJSONDecoder(t *testing.T) { for _, test := range tests { t.Run(test.path, func(t *testing.T) { f, err := os.Open("test-fixtures/spdx/" + test.path) - assert.NoError(t, err) + require.NoError(t, err) sbom, err := decoder(f) if test.fail { - assert.Error(t, err) + require.Error(t, err) return } else { - assert.NoError(t, err) + require.NoError(t, err) } if test.packages != nil { diff --git a/syft/formats/spdx22json/encoder.go b/syft/formats/spdxjson/encoder.go similarity index 71% rename from syft/formats/spdx22json/encoder.go rename to syft/formats/spdxjson/encoder.go index 36bfeb8a203..a8ca26648d8 100644 --- a/syft/formats/spdx22json/encoder.go +++ b/syft/formats/spdxjson/encoder.go @@ -1,14 +1,15 @@ -package spdx22json +package spdxjson import ( "encoding/json" "io" + "github.com/anchore/syft/syft/formats/common/spdxhelpers" "github.com/anchore/syft/syft/sbom" ) func encoder(output io.Writer, s sbom.SBOM) error { - doc := toFormatModel(s) + doc := spdxhelpers.ToFormatModel(s) enc := json.NewEncoder(output) // prevent > and < from being escaped in the payload diff --git a/syft/formats/spdx22json/encoder_test.go b/syft/formats/spdxjson/encoder_test.go similarity index 99% rename from syft/formats/spdx22json/encoder_test.go rename to syft/formats/spdxjson/encoder_test.go index 32968596863..5450136134a 100644 --- a/syft/formats/spdx22json/encoder_test.go +++ b/syft/formats/spdxjson/encoder_test.go @@ -1,4 +1,4 @@ -package spdx22json +package spdxjson import ( "flag" diff --git a/syft/formats/spdx22json/format.go b/syft/formats/spdxjson/format.go similarity index 92% rename from syft/formats/spdx22json/format.go rename to syft/formats/spdxjson/format.go index ae40169100f..6fc2ad78281 100644 --- a/syft/formats/spdx22json/format.go +++ b/syft/formats/spdxjson/format.go @@ -1,4 +1,4 @@ -package spdx22json +package spdxjson import ( "github.com/anchore/syft/syft/sbom" diff --git a/syft/formats/spdx22json/test-fixtures/image-simple/Dockerfile b/syft/formats/spdxjson/test-fixtures/image-simple/Dockerfile similarity index 100% rename from syft/formats/spdx22json/test-fixtures/image-simple/Dockerfile rename to syft/formats/spdxjson/test-fixtures/image-simple/Dockerfile diff --git a/syft/formats/spdx22json/test-fixtures/image-simple/file-1.txt b/syft/formats/spdxjson/test-fixtures/image-simple/file-1.txt similarity index 100% rename from syft/formats/spdx22json/test-fixtures/image-simple/file-1.txt rename to syft/formats/spdxjson/test-fixtures/image-simple/file-1.txt diff --git a/syft/formats/spdx22json/test-fixtures/image-simple/file-2.txt b/syft/formats/spdxjson/test-fixtures/image-simple/file-2.txt similarity index 100% rename from syft/formats/spdx22json/test-fixtures/image-simple/file-2.txt rename to syft/formats/spdxjson/test-fixtures/image-simple/file-2.txt diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden similarity index 57% rename from syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden rename to syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden index 31b2f4cd01c..c444dee2a3e 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden @@ -1,61 +1,66 @@ { + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "/some/path", - "spdxVersion": "SPDX-2.2", + "documentNamespace": "https://anchore.com/syft/dir/some/path-0f9b165e-1819-43cb-bd58-f61c1c23d6cf", "creationInfo": { - "created": "2022-11-07T14:11:33.934417Z", + "licenseListVersion": "3.18", "creators": [ "Organization: Anchore, Inc", "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": [ { - "SPDXID": "SPDXRef-1b1d0be59ac59d2c", "name": "package-1", - "licenseConcluded": "MIT", + "SPDXID": "SPDXRef-Package-python-package-1-1b1d0be59ac59d2c", + "versionInfo": "1.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "a-purl-2", - "referenceType": "purl" + "comment": "" } - ], - "filesAnalyzed": false, - "licenseDeclared": "MIT", - "sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1", - "versionInfo": "1.0.1" + ] }, { - "SPDXID": "SPDXRef-db4abfe497c180d3", "name": "package-2", - "licenseConcluded": "NONE", + "SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3", + "versionInfo": "2.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "pkg:deb/debian/package-2@2.0.1", - "referenceType": "purl" + "comment": "" } - ], - "filesAnalyzed": false, - "licenseDeclared": "NONE", - "sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1", - "versionInfo": "2.0.1" + ] } ] } diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden similarity index 60% rename from syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden rename to syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden index 38d015e9001..9d052f8ec57 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden @@ -1,61 +1,66 @@ { + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "user-image-input", - "spdxVersion": "SPDX-2.2", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-5841d063-c3ef-406b-91b4-8a702ef45ce9", "creationInfo": { - "created": "2022-11-07T14:11:33.941498Z", + "licenseListVersion": "3.18", "creators": [ "Organization: Anchore, Inc", "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": [ { - "SPDXID": "SPDXRef-66ba429119b8bec6", "name": "package-1", - "licenseConcluded": "MIT", + "SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "versionInfo": "1.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "a-purl-1", - "referenceType": "purl" + "comment": "" } - ], - "filesAnalyzed": false, - "licenseDeclared": "MIT", - "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt", - "versionInfo": "1.0.1" + ] }, { - "SPDXID": "SPDXRef-958443e2d9304af4", "name": "package-2", - "licenseConcluded": "NONE", + "SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4", + "versionInfo": "2.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "pkg:deb/debian/package-2@2.0.1", - "referenceType": "purl" + "comment": "" } - ], - "filesAnalyzed": false, - "licenseDeclared": "NONE", - "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", - "versionInfo": "2.0.1" + ] } ] } diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden similarity index 51% rename from syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden rename to syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 8591b9da8aa..93c474cad6c 100644 --- a/syft/formats/spdx22json/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -1,151 +1,160 @@ { + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "user-image-input", - "spdxVersion": "SPDX-2.2", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-8755f340-f205-4bf2-a909-94c623670734", "creationInfo": { - "created": "2022-11-07T14:11:33.947742Z", + "licenseListVersion": "3.18", "creators": [ "Organization: Anchore, Inc", "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": [ { - "SPDXID": "SPDXRef-66ba429119b8bec6", "name": "package-1", - "licenseConcluded": "MIT", + "SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "versionInfo": "1.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "a-purl-1", - "referenceType": "purl" + "comment": "" } - ], - "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", - "versionInfo": "1.0.1" + ] }, { - "SPDXID": "SPDXRef-958443e2d9304af4", "name": "package-2", - "licenseConcluded": "NONE", + "SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4", + "versionInfo": "2.0.1", "downloadLocation": "NOASSERTION", + "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", "externalRefs": [ { "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", - "referenceType": "cpe23Type" + "comment": "" }, { - "referenceCategory": "PACKAGE_MANAGER", + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", "referenceLocator": "pkg:deb/debian/package-2@2.0.1", - "referenceType": "purl" + "comment": "" } - ], - "filesAnalyzed": false, - "licenseDeclared": "NONE", - "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", - "versionInfo": "2.0.1" + ] } ], "files": [ { - "SPDXID": "SPDXRef-9c2f7510199b17f6", - "licenseConcluded": "NOASSERTION", "fileName": "/a1/f6", + "SPDXID": "SPDXRef-9c2f7510199b17f6", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" }, { - "SPDXID": "SPDXRef-c6f5b29dca12661f", - "licenseConcluded": "NOASSERTION", "fileName": "/d1/f3", + "SPDXID": "SPDXRef-c6f5b29dca12661f", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" }, { - "SPDXID": "SPDXRef-c641caa71518099f", - "licenseConcluded": "NOASSERTION", "fileName": "/d2/f4", + "SPDXID": "SPDXRef-c641caa71518099f", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" }, { - "SPDXID": "SPDXRef-5265a4dde3edbf7c", - "licenseConcluded": "NOASSERTION", "fileName": "/f1", + "SPDXID": "SPDXRef-5265a4dde3edbf7c", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" }, { - "SPDXID": "SPDXRef-f9e49132a4b96ccd", - "licenseConcluded": "NOASSERTION", "fileName": "/f2", + "SPDXID": "SPDXRef-f9e49132a4b96ccd", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" }, { - "SPDXID": "SPDXRef-839d99ee67d9d174", - "licenseConcluded": "NOASSERTION", "fileName": "/z1/f5", + "SPDXID": "SPDXRef-839d99ee67d9d174", "fileTypes": [ "OTHER" - ] + ], + "checksums": null, + "licenseConcluded": "NOASSERTION", + "copyrightText": "" } ], "relationships": [ { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c", + "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-839d99ee67d9d174" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd", + "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-9c2f7510199b17f6" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-c6f5b29dca12661f", + "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-c641caa71518099f" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-c641caa71518099f", + "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-c6f5b29dca12661f" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-839d99ee67d9d174", + "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-66ba429119b8bec6", - "relationshipType": "CONTAINS", - "relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd" + "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", + "relatedSpdxElement": "SPDXRef-9c2f7510199b17f6", + "relationshipType": "CONTAINS" } ] } diff --git a/syft/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden similarity index 84% rename from syft/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden rename to syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index 11a1958c893..0a4b4d25667 100644 Binary files a/syft/formats/spdx22json/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden and b/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden differ diff --git a/syft/formats/spdx22json/test-fixtures/spdx/alpine-3.10.syft.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/alpine-3.10.syft.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/alpine-3.10.syft.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/alpine-3.10.syft.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/alpine-3.10.vendor.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/alpine-3.10.vendor.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/alpine-3.10.vendor.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/alpine-3.10.vendor.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/bad/example7-bin.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/bad/example7-bin.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/bad/example7-bin.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/bad/example7-bin.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/bad/example7-go-module.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/bad/example7-go-module.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/bad/example7-go-module.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/bad/example7-go-module.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/bad/example7-golang.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/bad/example7-golang.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/bad/example7-golang.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/bad/example7-golang.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/bad/example7-third-party-modules.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/bad/example7-third-party-modules.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/bad/example7-third-party-modules.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/bad/example7-third-party-modules.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/example7-bin.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/example7-bin.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/example7-bin.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/example7-bin.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/example7-go-module.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/example7-go-module.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/example7-go-module.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/example7-go-module.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/example7-golang.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/example7-golang.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/example7-golang.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/example7-golang.spdx.json diff --git a/syft/formats/spdx22json/test-fixtures/spdx/example7-third-party-modules.spdx.json b/syft/formats/spdxjson/test-fixtures/spdx/example7-third-party-modules.spdx.json similarity index 100% rename from syft/formats/spdx22json/test-fixtures/spdx/example7-third-party-modules.spdx.json rename to syft/formats/spdxjson/test-fixtures/spdx/example7-third-party-modules.spdx.json diff --git a/syft/formats/spdx22json/validator.go b/syft/formats/spdxjson/validator.go similarity index 84% rename from syft/formats/spdx22json/validator.go rename to syft/formats/spdxjson/validator.go index 47366b1de5c..774309068ac 100644 --- a/syft/formats/spdx22json/validator.go +++ b/syft/formats/spdxjson/validator.go @@ -1,4 +1,4 @@ -package spdx22json +package spdxjson import ( "io" diff --git a/syft/formats/spdx22tagvalue/decoder.go b/syft/formats/spdxtagvalue/decoder.go similarity index 84% rename from syft/formats/spdx22tagvalue/decoder.go rename to syft/formats/spdxtagvalue/decoder.go index a54ff98f066..d13c07560a7 100644 --- a/syft/formats/spdx22tagvalue/decoder.go +++ b/syft/formats/spdxtagvalue/decoder.go @@ -1,4 +1,4 @@ -package spdx22tagvalue +package spdxtagvalue import ( "fmt" @@ -11,7 +11,7 @@ import ( ) func decoder(reader io.Reader) (*sbom.SBOM, error) { - doc, err := tvloader.Load2_2(reader) + doc, err := tvloader.Load2_3(reader) if err != nil { return nil, fmt.Errorf("unable to decode spdx-json: %w", err) } diff --git a/syft/formats/spdxtagvalue/encoder.go b/syft/formats/spdxtagvalue/encoder.go new file mode 100644 index 00000000000..e8ec9d6163a --- /dev/null +++ b/syft/formats/spdxtagvalue/encoder.go @@ -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) +} diff --git a/syft/formats/spdx22tagvalue/encoder_test.go b/syft/formats/spdxtagvalue/encoder_test.go similarity index 98% rename from syft/formats/spdx22tagvalue/encoder_test.go rename to syft/formats/spdxtagvalue/encoder_test.go index f479d2715eb..79f606ebd0c 100644 --- a/syft/formats/spdx22tagvalue/encoder_test.go +++ b/syft/formats/spdxtagvalue/encoder_test.go @@ -1,4 +1,4 @@ -package spdx22tagvalue +package spdxtagvalue import ( "flag" diff --git a/syft/formats/spdx22tagvalue/format.go b/syft/formats/spdxtagvalue/format.go similarity index 93% rename from syft/formats/spdx22tagvalue/format.go rename to syft/formats/spdxtagvalue/format.go index 8bd5856c5f8..4f17561e609 100644 --- a/syft/formats/spdx22tagvalue/format.go +++ b/syft/formats/spdxtagvalue/format.go @@ -1,4 +1,4 @@ -package spdx22tagvalue +package spdxtagvalue import ( "github.com/anchore/syft/syft/sbom" diff --git a/syft/formats/spdx22tagvalue/test-fixtures/image-simple/Dockerfile b/syft/formats/spdxtagvalue/test-fixtures/image-simple/Dockerfile similarity index 100% rename from syft/formats/spdx22tagvalue/test-fixtures/image-simple/Dockerfile rename to syft/formats/spdxtagvalue/test-fixtures/image-simple/Dockerfile diff --git a/syft/formats/spdx22tagvalue/test-fixtures/image-simple/file-1.txt b/syft/formats/spdxtagvalue/test-fixtures/image-simple/file-1.txt similarity index 100% rename from syft/formats/spdx22tagvalue/test-fixtures/image-simple/file-1.txt rename to syft/formats/spdxtagvalue/test-fixtures/image-simple/file-1.txt diff --git a/syft/formats/spdx22tagvalue/test-fixtures/image-simple/file-2.txt b/syft/formats/spdxtagvalue/test-fixtures/image-simple/file-2.txt similarity index 100% rename from syft/formats/spdx22tagvalue/test-fixtures/image-simple/file-2.txt rename to syft/formats/spdxtagvalue/test-fixtures/image-simple/file-2.txt diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden similarity index 73% rename from syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden rename to syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden index 3fc1312416c..71ef36533ee 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden @@ -1,12 +1,12 @@ -SPDXVersion: SPDX-2.2 +SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT 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 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-07T14:11:42Z +Created: 2022-11-11T19:25:16Z ##### Package: @at-sign @@ -14,6 +14,7 @@ PackageName: @at-sign SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from the following paths: PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION @@ -24,6 +25,7 @@ PackageName: some/slashes SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from the following paths: PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION @@ -34,6 +36,7 @@ PackageName: under_scores SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from the following paths: PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden similarity index 67% rename from syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden rename to syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden index 571c7c36a1c..6e2268072da 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden @@ -1,12 +1,12 @@ -SPDXVersion: SPDX-2.2 +SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT 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 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-07T14:11:42Z +Created: 2022-11-11T19:25:16Z ##### Package: package-2 @@ -15,11 +15,12 @@ SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3 PackageVersion: 2.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1 PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION 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 @@ -28,9 +29,10 @@ SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c PackageVersion: 1.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1 PackageLicenseConcluded: MIT PackageLicenseDeclared: MIT PackageCopyrightText: NOASSERTION ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:* -ExternalRef: PACKAGE_MANAGER purl a-purl-2 +ExternalRef: PACKAGE-MANAGER purl a-purl-2 diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden similarity index 71% rename from syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden rename to syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden index b13c79c3bfb..9638ebbd751 100644 --- a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden @@ -1,12 +1,12 @@ -SPDXVersion: SPDX-2.2 +SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT 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 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-07T14:11:42Z +Created: 2022-11-11T19:25:16Z ##### Package: package-2 @@ -15,11 +15,12 @@ SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4 PackageVersion: 2.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION 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 @@ -28,9 +29,10 @@ SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6 PackageVersion: 1.0.1 PackageDownloadLocation: NOASSERTION FilesAnalyzed: false +PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt PackageLicenseConcluded: MIT PackageLicenseDeclared: MIT PackageCopyrightText: NOASSERTION ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:* -ExternalRef: PACKAGE_MANAGER purl a-purl-1 +ExternalRef: PACKAGE-MANAGER purl a-purl-1 diff --git a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden similarity index 84% rename from syft/formats/spdx22tagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden rename to syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index 11a1958c893..0a4b4d25667 100644 Binary files a/syft/formats/spdx22tagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden and b/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden differ diff --git a/syft/formats/spdx22tagvalue/validator.go b/syft/formats/spdxtagvalue/validator.go similarity index 81% rename from syft/formats/spdx22tagvalue/validator.go rename to syft/formats/spdxtagvalue/validator.go index 1a5c078bc33..e68875f2ecf 100644 --- a/syft/formats/spdx22tagvalue/validator.go +++ b/syft/formats/spdxtagvalue/validator.go @@ -1,4 +1,4 @@ -package spdx22tagvalue +package spdxtagvalue import ( "io" diff --git a/test/integration/convert_test.go b/test/integration/convert_test.go index c50d4fd098b..00f3f6e6a62 100644 --- a/test/integration/convert_test.go +++ b/test/integration/convert_test.go @@ -13,8 +13,8 @@ import ( "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" - "github.com/anchore/syft/syft/formats/spdx22json" - "github.com/anchore/syft/syft/formats/spdx22tagvalue" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/sbom" @@ -23,8 +23,8 @@ import ( var convertibleFormats = []sbom.Format{ syftjson.Format(), - spdx22json.Format(), - spdx22tagvalue.Format(), + spdxjson.Format(), + spdxtagvalue.Format(), cyclonedxjson.Format(), cyclonedxxml.Format(), }