diff --git a/syft/format/cyclonedxjson/encoder_test.go b/syft/format/cyclonedxjson/encoder_test.go index 00ece4a8a..8b57de976 100644 --- a/syft/format/cyclonedxjson/encoder_test.go +++ b/syft/format/cyclonedxjson/encoder_test.go @@ -114,6 +114,20 @@ func TestCycloneDxImageEncoder(t *testing.T) { ) } +func TestCycloneDxSourcePackageEncoder(t *testing.T) { + dir := t.TempDir() + testutil.AssertEncoderAgainstGoldenSnapshot(t, + testutil.EncoderSnapshotTestConfig{ + Subject: testutil.SourcePackagesInput(t, dir), + Format: getEncoder(t), + UpdateSnapshot: *updateSnapshot, + PersistRedactionsInSnapshot: true, + IsJSON: true, + Redactor: redactor(dir), + }, + ) +} + func redactor(values ...string) testutil.Redactor { return testutil.NewRedactions(). WithValuesRedacted(values...). diff --git a/syft/format/cyclonedxjson/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden b/syft/format/cyclonedxjson/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden new file mode 100644 index 000000000..4ca6b7c5d --- /dev/null +++ b/syft/format/cyclonedxjson/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden @@ -0,0 +1,287 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "serialNumber": "urn:uuid:redacted", + "version": 1, + "metadata": { + "timestamp": "timestamp:redacted", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "v0.42.0-bogus" + } + ] + }, + "component": { + "bom-ref": "redacted", + "type": "file", + "name": "some/path" + } + }, + "components": [ + { + "bom-ref": "pkg:deb/debian/attr@1%3A2.4.47-2%2Bb1?arch=amd64&distro=debian-12&package-id=5cc0e8979d7f5115&upstream=attr%401%3A2.4.47-2", + "type": "library", + "name": "attr", + "version": "1:2.4.47-2+b1", + "purl": "pkg:deb/debian/attr@1%3A2.4.47-2%2Bb1?arch=amd64&distro=debian-12&upstream=attr%401%3A2.4.47-2", + "externalReferences": [ + { + "url": "pkg:deb/debian/attr@1%3A2.4.47-2?arch=source&distro=debian-12", + "type": "source-distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "dpkg-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "deb" + }, + { + "name": "syft:package:metadataType", + "value": "dpkg-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/var/lib/dpkg/status" + }, + { + "name": "syft:metadata:installedSize", + "value": "0" + }, + { + "name": "syft:metadata:source", + "value": "attr" + }, + { + "name": "syft:metadata:sourceVersion", + "value": "1:2.4.47-2" + } + ] + }, + { + "bom-ref": "pkg:rpm/centos/bind-export-libs@9.11.13-3.el8?arch=x86_64&distro=centos-8&epoch=32&package-id=6008fca05ca7b418&upstream=bind-9.11.13-3.el8.src.rpm", + "type": "library", + "name": "bind-export-libs", + "version": "32:9.11.13-3.el8", + "purl": "pkg:rpm/centos/bind-export-libs@9.11.13-3.el8?arch=x86_64&distro=centos-8&epoch=32&upstream=bind-9.11.13-3.el8.src.rpm", + "externalReferences": [ + { + "url": "pkg:rpm/centos/bind@9.11.13-3.el8?arch=source&distro=centos-8&epoch=32", + "type": "source-distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rpm-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "rpm" + }, + { + "name": "syft:package:metadataType", + "value": "rpm-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/var/lib/rpm/Packages" + }, + { + "name": "syft:metadata:epoch", + "value": "32" + }, + { + "name": "syft:metadata:release", + "value": "3.el8" + }, + { + "name": "syft:metadata:size", + "value": "0" + }, + { + "name": "syft:metadata:sourceRpm", + "value": "bind-9.11.13-3.el8.src.rpm" + } + ] + }, + { + "bom-ref": "pkg:apk/alpine/busybox@1.35.0-r17?arch=x86_64&distro=alpine-3.16.3&package-id=aa74af1190de1c40", + "type": "library", + "name": "busybox", + "version": "1.35.0-r17", + "purl": "pkg:apk/alpine/busybox@1.35.0-r17?arch=x86_64&distro=alpine-3.16.3", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "apk-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "apk" + }, + { + "name": "syft:package:metadataType", + "value": "apk-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/lib/apk/db/installed" + }, + { + "name": "syft:metadata:installedSize", + "value": "0" + }, + { + "name": "syft:metadata:originPackage", + "value": "busybox" + }, + { + "name": "syft:metadata:size", + "value": "0" + } + ] + }, + { + "bom-ref": "pkg:alpm/arch/gcc-libs@13.2.1-3?arch=x86_64&distro=arch-rolling&package-id=58a2fcf749a9a3cc&upstream=gcc", + "type": "library", + "name": "gcc-libs", + "version": "13.2.1-3", + "purl": "pkg:alpm/arch/gcc-libs@13.2.1-3?arch=x86_64&distro=arch-rolling&upstream=gcc", + "externalReferences": [ + { + "url": "pkg:alpm/arch/gcc@13.2.1-3?arch=source&distro=arch-rolling", + "type": "source-distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "alpm-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "alpm" + }, + { + "name": "syft:package:metadataType", + "value": "alpm-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/var/lib/pacman/local/gcc-libs-13.2.1-3/desc" + }, + { + "name": "syft:metadata:architecture", + "value": "x86_64" + }, + { + "name": "syft:metadata:basepackage", + "value": "gcc" + }, + { + "name": "syft:metadata:package", + "value": "gcc-libs" + }, + { + "name": "syft:metadata:size", + "value": "0" + }, + { + "name": "syft:metadata:version", + "value": "13.2.1-3" + } + ] + }, + { + "bom-ref": "pkg:apk/alpine/libc-utils@0.7.2-r3?arch=x86_64&distro=alpine-3.16.3&package-id=16a8c7b19fde6f4f&upstream=libc-dev", + "type": "library", + "name": "libc-utils", + "version": "0.7.2-r3", + "purl": "pkg:apk/alpine/libc-utils@0.7.2-r3?arch=x86_64&distro=alpine-3.16.3&upstream=libc-dev", + "externalReferences": [ + { + "url": "pkg:apk/alpine/libc-dev@0.7.2-r3?arch=source&distro=alpine-3.16.3", + "type": "source-distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "apk-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "apk" + }, + { + "name": "syft:package:metadataType", + "value": "apk-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/lib/apk/db/installed" + }, + { + "name": "syft:metadata:installedSize", + "value": "0" + }, + { + "name": "syft:metadata:originPackage", + "value": "libc-dev" + }, + { + "name": "syft:metadata:size", + "value": "0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpam-runtime@1.5.2-6%2Bdeb12u1?arch=all&distro=debian-12&package-id=7b4ddc97df57aec4&upstream=pam", + "type": "library", + "name": "libpam-runtime", + "version": "1.5.2-6+deb12u1", + "purl": "pkg:deb/debian/libpam-runtime@1.5.2-6%2Bdeb12u1?arch=all&distro=debian-12&upstream=pam", + "externalReferences": [ + { + "url": "pkg:deb/debian/pam@1.5.2-6%2Bdeb12u1?arch=source&distro=debian-12", + "type": "source-distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "dpkg-db-cataloger" + }, + { + "name": "syft:package:type", + "value": "deb" + }, + { + "name": "syft:package:metadataType", + "value": "dpkg-db-entry" + }, + { + "name": "syft:location:0:path", + "value": "/var/lib/dpkg/status" + }, + { + "name": "syft:metadata:installedSize", + "value": "0" + }, + { + "name": "syft:metadata:source", + "value": "pam" + } + ] + } + ] +} diff --git a/syft/format/cyclonedxxml/encoder_test.go b/syft/format/cyclonedxxml/encoder_test.go index 5f25bec76..c38bd1363 100644 --- a/syft/format/cyclonedxxml/encoder_test.go +++ b/syft/format/cyclonedxxml/encoder_test.go @@ -87,6 +87,20 @@ func TestCycloneDxImageEncoder(t *testing.T) { ) } +func TestCycloneDxSourcePackageEncoder(t *testing.T) { + dir := t.TempDir() + testutil.AssertEncoderAgainstGoldenSnapshot(t, + testutil.EncoderSnapshotTestConfig{ + Subject: testutil.SourcePackagesInput(t, dir), + Format: getEncoder(t), + UpdateSnapshot: *updateSnapshot, + PersistRedactionsInSnapshot: true, + IsJSON: false, + Redactor: redactor(dir), + }, + ) +} + func redactor(values ...string) testutil.Redactor { return testutil.NewRedactions(). WithValuesRedacted(values...). diff --git a/syft/format/cyclonedxxml/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden b/syft/format/cyclonedxxml/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden new file mode 100644 index 000000000..d659b4a71 --- /dev/null +++ b/syft/format/cyclonedxxml/testdata/snapshot/TestCycloneDxSourcePackageEncoder.golden @@ -0,0 +1,131 @@ + + + + redacted + + + + anchore + syft + v0.42.0-bogus + + + + + some/path + + + + + attr + 1:2.4.47-2+b1 + pkg:deb/debian/attr@1%3A2.4.47-2%2Bb1?arch=amd64&distro=debian-12&upstream=attr%401%3A2.4.47-2 + + + pkg:deb/debian/attr@1%3A2.4.47-2?arch=source&distro=debian-12 + + + + dpkg-db-cataloger + deb + dpkg-db-entry + /var/lib/dpkg/status + 0 + attr + 1:2.4.47-2 + + + + bind-export-libs + 32:9.11.13-3.el8 + pkg:rpm/centos/bind-export-libs@9.11.13-3.el8?arch=x86_64&distro=centos-8&epoch=32&upstream=bind-9.11.13-3.el8.src.rpm + + + pkg:rpm/centos/bind@9.11.13-3.el8?arch=source&distro=centos-8&epoch=32 + + + + rpm-db-cataloger + rpm + rpm-db-entry + /var/lib/rpm/Packages + 32 + 3.el8 + 0 + bind-9.11.13-3.el8.src.rpm + + + + busybox + 1.35.0-r17 + pkg:apk/alpine/busybox@1.35.0-r17?arch=x86_64&distro=alpine-3.16.3 + + apk-db-cataloger + apk + apk-db-entry + /lib/apk/db/installed + 0 + busybox + 0 + + + + gcc-libs + 13.2.1-3 + pkg:alpm/arch/gcc-libs@13.2.1-3?arch=x86_64&distro=arch-rolling&upstream=gcc + + + pkg:alpm/arch/gcc@13.2.1-3?arch=source&distro=arch-rolling + + + + alpm-db-cataloger + alpm + alpm-db-entry + /var/lib/pacman/local/gcc-libs-13.2.1-3/desc + x86_64 + gcc + gcc-libs + 0 + 13.2.1-3 + + + + libc-utils + 0.7.2-r3 + pkg:apk/alpine/libc-utils@0.7.2-r3?arch=x86_64&distro=alpine-3.16.3&upstream=libc-dev + + + pkg:apk/alpine/libc-dev@0.7.2-r3?arch=source&distro=alpine-3.16.3 + + + + apk-db-cataloger + apk + apk-db-entry + /lib/apk/db/installed + 0 + libc-dev + 0 + + + + libpam-runtime + 1.5.2-6+deb12u1 + pkg:deb/debian/libpam-runtime@1.5.2-6%2Bdeb12u1?arch=all&distro=debian-12&upstream=pam + + + pkg:deb/debian/pam@1.5.2-6%2Bdeb12u1?arch=source&distro=debian-12 + + + + dpkg-db-cataloger + deb + dpkg-db-entry + /var/lib/dpkg/status + 0 + pam + + + + \ No newline at end of file diff --git a/syft/format/internal/testutil/source_package_input.go b/syft/format/internal/testutil/source_package_input.go new file mode 100644 index 000000000..afc1bdc7e --- /dev/null +++ b/syft/format/internal/testutil/source_package_input.go @@ -0,0 +1,146 @@ +package testutil + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/sbom" + "github.com/anchore/syft/syft/source/directorysource" +) + +func SourcePackagesInput(t testing.TB, dir string) sbom.SBOM { + path := filepath.Join(dir, "some", "path") + require.NoError(t, os.MkdirAll(path, 0755)) + + src, err := directorysource.New( + directorysource.Config{ + Path: path, + Base: dir, + }, + ) + require.NoError(t, err) + + return sbom.SBOM{ + Artifacts: sbom.Artifacts{ + Packages: newSourcePackageCatalog(), + }, + Source: src.Describe(), + Descriptor: sbom.Descriptor{ + Name: "syft", + Version: "v0.42.0-bogus", + }, + } +} + +//nolint:funlen +func newSourcePackageCatalog() *pkg.Collection { + epoch := 32 + + catalog := pkg.NewCollection() + + // apk: the origin differs from the package name, and shares its version + catalog.Add(pkg.Package{ + Name: "libc-utils", + Version: "0.7.2-r3", + Type: pkg.ApkPkg, + FoundBy: "apk-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/lib/apk/db/installed")), + PURL: "pkg:apk/alpine/libc-utils@0.7.2-r3?arch=x86_64&distro=alpine-3.16.3&upstream=libc-dev", + Metadata: pkg.ApkDBEntry{ + Package: "libc-utils", + OriginPackage: "libc-dev", + Version: "0.7.2-r3", + Architecture: "x86_64", + }, + }) + + // apk: negative control -- the package is its own origin, so no source reference is expected + catalog.Add(pkg.Package{ + Name: "busybox", + Version: "1.35.0-r17", + Type: pkg.ApkPkg, + FoundBy: "apk-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/lib/apk/db/installed")), + PURL: "pkg:apk/alpine/busybox@1.35.0-r17?arch=x86_64&distro=alpine-3.16.3", + Metadata: pkg.ApkDBEntry{ + Package: "busybox", + OriginPackage: "busybox", + Version: "1.35.0-r17", + Architecture: "x86_64", + }, + }) + + // deb: source name differs, and the source version is implied by the binary version + catalog.Add(pkg.Package{ + Name: "libpam-runtime", + Version: "1.5.2-6+deb12u1", + Type: pkg.DebPkg, + FoundBy: "dpkg-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/var/lib/dpkg/status")), + PURL: "pkg:deb/debian/libpam-runtime@1.5.2-6%2Bdeb12u1?arch=all&distro=debian-12&upstream=pam", + Metadata: pkg.DpkgDBEntry{ + Package: "libpam-runtime", + Version: "1.5.2-6+deb12u1", + Source: "pam", + Architecture: "all", + }, + }) + + // deb: the source version differs from the binary version + catalog.Add(pkg.Package{ + Name: "attr", + Version: "1:2.4.47-2+b1", + Type: pkg.DebPkg, + FoundBy: "dpkg-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/var/lib/dpkg/status")), + PURL: "pkg:deb/debian/attr@1%3A2.4.47-2%2Bb1?arch=amd64&distro=debian-12&upstream=attr%401%3A2.4.47-2", + Metadata: pkg.DpkgDBEntry{ + Package: "attr", + Version: "1:2.4.47-2+b1", + Source: "attr", + SourceVersion: "1:2.4.47-2", + Architecture: "amd64", + }, + }) + + // rpm: name and version both come out of the source RPM filename, and the epoch is carried over + catalog.Add(pkg.Package{ + Name: "bind-export-libs", + Version: "32:9.11.13-3.el8", + Type: pkg.RpmPkg, + FoundBy: "rpm-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/var/lib/rpm/Packages")), + PURL: "pkg:rpm/centos/bind-export-libs@9.11.13-3.el8?arch=x86_64&distro=centos-8&epoch=32&upstream=bind-9.11.13-3.el8.src.rpm", + Metadata: pkg.RpmDBEntry{ + Name: "bind-export-libs", + Version: "9.11.13", + Release: "3.el8", + Epoch: &epoch, + Arch: "x86_64", + SourceRpm: "bind-9.11.13-3.el8.src.rpm", + }, + }) + + // alpm: the base package differs from the package name, and shares its version + catalog.Add(pkg.Package{ + Name: "gcc-libs", + Version: "13.2.1-3", + Type: pkg.AlpmPkg, + FoundBy: "alpm-db-cataloger", + Locations: file.NewLocationSet(file.NewLocation("/var/lib/pacman/local/gcc-libs-13.2.1-3/desc")), + PURL: "pkg:alpm/arch/gcc-libs@13.2.1-3?arch=x86_64&distro=arch-rolling&upstream=gcc", + Metadata: pkg.AlpmDBEntry{ + Package: "gcc-libs", + Version: "13.2.1-3", + BasePackage: "gcc", + Architecture: "x86_64", + }, + }) + + return catalog +}