diff --git a/syft/formats/common/spdxhelpers/relationship_type.go b/syft/formats/common/spdxhelpers/relationship_type.go index 564dd32ec..a3234c873 100644 --- a/syft/formats/common/spdxhelpers/relationship_type.go +++ b/syft/formats/common/spdxhelpers/relationship_type.go @@ -8,6 +8,11 @@ const ( // Example: The package 'WildFly' is described by SPDX document WildFly.spdx. DescribedByRelationship RelationshipType = "DESCRIBED_BY" + // Describes is to be used when SPDXRef-DOCUMENT describes SPDXRef-A. + // Example: An SPDX document WildFly.spdx describes package ‘WildFly’. + // Note this is a logical relationship to help organize related items within an SPDX document that is mandatory if more than one package or set of files (not in a package) is present. + DescribesRelationship RelationshipType = "DESCRIBES" + // ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B. // Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c. ContainsRelationship RelationshipType = "CONTAINS" diff --git a/syft/formats/common/spdxhelpers/to_format_model.go b/syft/formats/common/spdxhelpers/to_format_model.go index 798bd1a27..655c9cce6 100644 --- a/syft/formats/common/spdxhelpers/to_format_model.go +++ b/syft/formats/common/spdxhelpers/to_format_model.go @@ -33,6 +33,24 @@ const ( //nolint:funlen func ToFormatModel(s sbom.SBOM) *spdx.Document { name, namespace := DocumentNameAndNamespace(s.Source) + relationships := toRelationships(s.RelationshipsSorted()) + + // for valid SPDX we need a document describes relationship + // TODO: remove this placeholder after deciding on correct behavior + // for the primary package purpose field: + // https://spdx.github.io/spdx-spec/v2.3/package-information/#724-primary-package-purpose-field + documentDescribesRelationship := &spdx.Relationship{ + RefA: common.DocElementID{ + ElementRefID: "DOCUMENT", + }, + Relationship: string(DescribesRelationship), + RefB: common.DocElementID{ + ElementRefID: "DOCUMENT", + }, + RelationshipComment: "", + } + + relationships = append(relationships, documentDescribesRelationship) return &spdx.Document{ // 6.1: SPDX Version; should be in the format "SPDX-x.x" @@ -107,7 +125,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document { }, Packages: toPackages(s.Artifacts.PackageCatalog, s), Files: toFiles(s), - Relationships: toRelationships(s.RelationshipsSorted()), + Relationships: relationships, } } @@ -407,6 +425,15 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) { digests = digestsForLocation } + // if we don't have any metadata or digests for this location + // then the file is most likely a symlink or non-regular file + // for now we include a 0 sha1 digest as requested by the spdx spec + // TODO: update location code in core SBOM so that we can map complex links + // back to their real file digest location. + if len(digests) == 0 { + digests = append(digests, file.Digest{Algorithm: "sha1", Value: "0000000000000000000000000000000000000000"}) + } + // TODO: add file classifications (?) and content as a snippet var comment string diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden index bbe863815..59bdc67c9 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden @@ -3,14 +3,14 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "/some/path", - "documentNamespace": "https://anchore.com/syft/dir/some/path-4bf54cdd-0a0f-4560-bf4f-39cac2ef7a5b", + "documentNamespace": "https://anchore.com/syft/dir/some/path-116e86ba-a976-48ed-909d-9278807ee7fe", "creationInfo": { - "licenseListVersion": "3.18", + "licenseListVersion": "3.19", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" ], - "created": "2022-11-19T13:46:57Z", + "created": "2022-12-21T03:39:05Z", "comment": "" }, "packages": [ @@ -62,5 +62,12 @@ } ] } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DOCUMENT", + "relationshipType": "DESCRIBES" + } ] } diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden index 5462bcdf1..8e9ca38cc 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden @@ -3,14 +3,14 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "user-image-input", - "documentNamespace": "https://anchore.com/syft/image/user-image-input-102ca7dc-3d1e-46d2-b130-28968831ebcc", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-006b9b96-66f1-4de3-897f-6583b4358c87", "creationInfo": { - "licenseListVersion": "3.18", + "licenseListVersion": "3.19", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" ], - "created": "2022-11-19T13:46:57Z", + "created": "2022-12-21T03:39:05Z", "comment": "" }, "packages": [ @@ -62,5 +62,12 @@ } ] } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DOCUMENT", + "relationshipType": "DESCRIBES" + } ] } diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 451f76e9b..e88d0f5e6 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -3,14 +3,14 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "user-image-input", - "documentNamespace": "https://anchore.com/syft/image/user-image-input-ace88a38-4633-4bff-8fa3-8ae929dab37d", + "documentNamespace": "https://anchore.com/syft/image/user-image-input-552a9cfc-49ee-4706-81cb-723fd7146b4f", "creationInfo": { "licenseListVersion": "3.19", "creators": [ "Organization: Anchore, Inc", "Tool: syft-v0.42.0-bogus" ], - "created": "2022-12-14T18:21:40Z", + "created": "2022-12-21T03:39:05Z", "comment": "" }, "packages": [ @@ -70,7 +70,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" }, @@ -80,7 +85,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" }, @@ -90,7 +100,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" }, @@ -100,7 +115,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" }, @@ -110,7 +130,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" }, @@ -120,7 +145,12 @@ "fileTypes": [ "OTHER" ], - "checksums": [], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], "licenseConcluded": "NOASSERTION", "copyrightText": "" } @@ -155,6 +185,11 @@ "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6", "relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd", "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DOCUMENT", + "relationshipType": "DESCRIBES" } ] } diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index aca661202..b9ddf6e33 100644 Binary files a/syft/formats/spdxjson/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/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden index 988de2374..c017916c2 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden @@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: foobar/baz -DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-62bc0aae-2b37-4c86-ab79-63c6fc4198ed -LicenseListVersion: 3.18 +DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-478e410d-7fad-472c-b4e9-a4068ef28160 +LicenseListVersion: 3.19 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-19T13:48:30Z +Created: 2022-12-21T03:39:05Z ##### Package: @at-sign @@ -41,3 +41,7 @@ PackageLicenseConcluded: NONE PackageLicenseDeclared: NONE PackageCopyrightText: NOASSERTION +##### Relationships + +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT + diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index d87fee378..94cd399de 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -2,42 +2,48 @@ SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: user-image-input -DocumentNamespace: https://anchore.com/syft/image/user-image-input-cc20e416-9c74-401c-b4aa-245556bada5e -LicenseListVersion: 3.18 +DocumentNamespace: https://anchore.com/syft/image/user-image-input-73433e8c-364f-42b6-b5b7-9a4da8799868 +LicenseListVersion: 3.19 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-19T13:48:30Z +Created: 2022-12-21T03:39:05Z ##### Unpackaged files FileName: /f1 SPDXID: SPDXRef-5265a4dde3edbf7c FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION FileName: /z1/f5 SPDXID: SPDXRef-839d99ee67d9d174 FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION FileName: /a1/f6 SPDXID: SPDXRef-9c2f7510199b17f6 FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION FileName: /d2/f4 SPDXID: SPDXRef-c641caa71518099f FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION FileName: /d1/f3 SPDXID: SPDXRef-c6f5b29dca12661f FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION FileName: /f2 SPDXID: SPDXRef-f9e49132a4b96ccd FileType: OTHER +FileChecksum: SHA1: 0000000000000000000000000000000000000000 LicenseConcluded: NOASSERTION ##### Package: package-2 @@ -76,4 +82,5 @@ Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c641caa71518099f Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c6f5b29dca12661f Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-f9e49132a4b96ccd +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden index 119e225ef..7bd71f05f 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden @@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: /some/path -DocumentNamespace: https://anchore.com/syft/dir/some/path-7a4b2140-6669-4a28-80dd-5c8e795c5da0 -LicenseListVersion: 3.18 +DocumentNamespace: https://anchore.com/syft/dir/some/path-1d303762-46d2-47b5-9c81-defa91387275 +LicenseListVersion: 3.19 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-19T13:48:30Z +Created: 2022-12-21T03:39:05Z ##### Package: package-2 @@ -36,3 +36,7 @@ PackageCopyrightText: NOASSERTION ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:* ExternalRef: PACKAGE-MANAGER purl a-purl-2 +##### Relationships + +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT + diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden index 3d62f6ccc..df1cb1467 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden @@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: user-image-input -DocumentNamespace: https://anchore.com/syft/image/user-image-input-baff7ada-85cb-403e-90d7-05b0c6d79490 -LicenseListVersion: 3.18 +DocumentNamespace: https://anchore.com/syft/image/user-image-input-559af225-63af-4bc0-94fb-bce94913bcfa +LicenseListVersion: 3.19 Creator: Organization: Anchore, Inc Creator: Tool: syft-v0.42.0-bogus -Created: 2022-11-19T13:48:30Z +Created: 2022-12-21T03:39:05Z ##### Package: package-2 @@ -36,3 +36,7 @@ PackageCopyrightText: NOASSERTION ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:* ExternalRef: PACKAGE-MANAGER purl a-purl-1 +##### Relationships + +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT + diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index 0a4b4d256..b9ddf6e33 100644 Binary files a/syft/formats/spdxtagvalue/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/test/cli/spdx_tooling_validation_test.go b/test/cli/spdx_tooling_validation_test.go index 00c9afedd..eef290dee 100644 --- a/test/cli/spdx_tooling_validation_test.go +++ b/test/cli/spdx_tooling_validation_test.go @@ -6,6 +6,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -26,6 +27,29 @@ func TestSpdxValidationTooling(t *testing.T) { images: []string{"alpine:latest", "photon:3.0", "debian:latest"}, env: map[string]string{ "SYFT_FILE_METADATA_CATALOGER_ENABLED": "true", + "SYFT_FILE_CONTENTS_CATALOGER_ENABLED": "true", + "SYFT_FILE_METADATA_DIGESTS": "sha1", + }, + setup: func(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + fixturesPath := filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools") + buildCmd := exec.Command("make", "build") + buildCmd.Dir = fixturesPath + err = buildCmd.Run() + require.NoError(t, err) + }, + assertions: []traitAssertion{ + assertSuccessfulReturnCode, + }, + }, + { + name: "spdx validation tooling json", + syftArgs: []string{"packages", "-o", "spdx-json"}, + images: []string{"alpine:latest", "photon:3.0", "debian:latest"}, + env: map[string]string{ + "SYFT_FILE_METADATA_CATALOGER_ENABLED": "true", + "SYFT_FILE_CONTENTS_CATALOGER_ENABLED": "true", "SYFT_FILE_METADATA_DIGESTS": "sha1", }, setup: func(t *testing.T) { @@ -61,8 +85,15 @@ func TestSpdxValidationTooling(t *testing.T) { f, err := os.CreateTemp(t.TempDir(), "temp") require.NoError(t, err) + var suffix string + if strings.Contains(test.name, "json") { + suffix = ".json" + } else { + suffix = ".spdx" + } + // spdx tooling only takes a file with suffix spdx - rename := path.Join(path.Dir(f.Name()), fmt.Sprintf("%s.spdx", path.Base(f.Name()))) + rename := path.Join(path.Dir(f.Name()), fmt.Sprintf("%s.%s", path.Base(f.Name()), suffix)) err = os.Rename(f.Name(), rename) require.NoError(t, err)