From f13f6982b6b2453ea6c51443d19d1bbe82d72380 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Mon, 24 Jan 2022 18:13:02 -0500 Subject: [PATCH] encode upstream qualifier on os package pURLs Signed-off-by: Alex Goodman --- syft/pkg/apk_metadata.go | 12 +++++++--- syft/pkg/apk_metadata_test.go | 16 ++++++++++++- syft/pkg/dpkg_metadata.go | 18 ++++++++++++--- syft/pkg/dpkg_metadata_test.go | 30 +++++++++++++++++++++--- syft/pkg/python_package_metadata.go | 2 +- syft/pkg/rpmdb_metadata.go | 4 ++++ syft/pkg/rpmdb_metadata_test.go | 36 ++++++++++++++++++++--------- syft/pkg/url.go | 5 +++- 8 files changed, 100 insertions(+), 23 deletions(-) diff --git a/syft/pkg/apk_metadata.go b/syft/pkg/apk_metadata.go index 7c3db6bb206..866c080ee1d 100644 --- a/syft/pkg/apk_metadata.go +++ b/syft/pkg/apk_metadata.go @@ -49,6 +49,14 @@ type ApkFileRecord struct { // PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec) func (m ApkMetadata) PackageURL(distro *linux.Release) string { + qualifiers := map[string]string{ + purlArchQualifier: m.Architecture, + } + + if m.OriginPackage != "" { + qualifiers[purlUpstreamQualifier] = m.OriginPackage + } + return packageurl.NewPackageURL( // note: this is currently a candidate and not technically within spec // see https://github.com/package-url/purl-spec#other-candidate-types-to-define @@ -57,9 +65,7 @@ func (m ApkMetadata) PackageURL(distro *linux.Release) string { m.Package, m.Version, purlQualifiers( - map[string]string{ - purlArchQualifier: m.Architecture, - }, + qualifiers, distro, ), "", diff --git a/syft/pkg/apk_metadata_test.go b/syft/pkg/apk_metadata_test.go index 774b941474f..e1531cd56ad 100644 --- a/syft/pkg/apk_metadata_test.go +++ b/syft/pkg/apk_metadata_test.go @@ -31,7 +31,7 @@ func TestApkMetadata_pURL(t *testing.T) { expected: "pkg:alpine/p@v?arch=a&distro=alpine-3.4.6", }, { - name: "missing architecure", + name: "missing architecture", metadata: ApkMetadata{ Package: "p", Version: "v", @@ -67,6 +67,20 @@ func TestApkMetadata_pURL(t *testing.T) { }, expected: "pkg:alpine/g%20plus%20plus@v84?arch=am86&distro=alpine-3.15.0", }, + { + name: "add source information as qualifier", + metadata: ApkMetadata{ + Package: "p", + Version: "v", + Architecture: "a", + OriginPackage: "origin", + }, + distro: linux.Release{ + ID: "alpine", + VersionID: "3.4.6", + }, + expected: "pkg:alpine/p@v?arch=a&upstream=origin&distro=alpine-3.4.6", + }, } for _, test := range tests { diff --git a/syft/pkg/dpkg_metadata.go b/syft/pkg/dpkg_metadata.go index d2687c0ebe0..93f6cab5e2b 100644 --- a/syft/pkg/dpkg_metadata.go +++ b/syft/pkg/dpkg_metadata.go @@ -1,6 +1,7 @@ package pkg import ( + "fmt" "sort" "github.com/anchore/syft/syft/file" @@ -43,6 +44,19 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string { if distro != nil { namespace = distro.ID } + + qualifiers := map[string]string{ + purlArchQualifier: m.Architecture, + } + + if m.Source != "" { + if m.SourceVersion != "" { + qualifiers[purlUpstreamQualifier] = fmt.Sprintf("%s@%s", m.Source, m.SourceVersion) + } else { + qualifiers[purlUpstreamQualifier] = m.Source + } + } + return packageurl.NewPackageURL( // TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21 // TODO: or, since we're now using an Anchore fork of this module, we could do this sooner. @@ -51,9 +65,7 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string { m.Package, m.Version, purlQualifiers( - map[string]string{ - purlArchQualifier: m.Architecture, - }, + qualifiers, distro, ), "", diff --git a/syft/pkg/dpkg_metadata_test.go b/syft/pkg/dpkg_metadata_test.go index f056a72ffb4..ce6f1363c42 100644 --- a/syft/pkg/dpkg_metadata_test.go +++ b/syft/pkg/dpkg_metadata_test.go @@ -25,7 +25,6 @@ func TestDpkgMetadata_pURL(t *testing.T) { }, metadata: DpkgMetadata{ Package: "p", - Source: "s", Version: "v", }, expected: "pkg:deb/debian/p@v?distro=debian-11", @@ -38,7 +37,6 @@ func TestDpkgMetadata_pURL(t *testing.T) { }, metadata: DpkgMetadata{ Package: "p", - Source: "s", Version: "v", Architecture: "a", }, @@ -48,11 +46,37 @@ func TestDpkgMetadata_pURL(t *testing.T) { name: "missing distro", metadata: DpkgMetadata{ Package: "p", - Source: "s", Version: "v", }, expected: "pkg:deb/p@v", }, + { + name: "with upstream qualifier with source pkg name info", + distro: &linux.Release{ + ID: "debian", + VersionID: "11", + }, + metadata: DpkgMetadata{ + Package: "p", + Source: "s", + Version: "v", + }, + expected: "pkg:deb/debian/p@v?upstream=s&distro=debian-11", + }, + { + name: "with upstream qualifier with source pkg name and version info", + distro: &linux.Release{ + ID: "debian", + VersionID: "11", + }, + metadata: DpkgMetadata{ + Package: "p", + Source: "s", + Version: "v", + SourceVersion: "2.3", + }, + expected: "pkg:deb/debian/p@v?upstream=s@2.3&distro=debian-11", + }, } for _, test := range tests { diff --git a/syft/pkg/python_package_metadata.go b/syft/pkg/python_package_metadata.go index 25b311cf96f..125b9a4c28c 100644 --- a/syft/pkg/python_package_metadata.go +++ b/syft/pkg/python_package_metadata.go @@ -106,7 +106,7 @@ func (p PythonDirectURLOriginInfo) vcsURLQualifier() packageurl.Qualifiers { if p.VCS != "" { // Taken from https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs // packageurl-go still doesn't support all qualifier names - return packageurl.Qualifiers{{Key: purlVCSURL, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}} + return packageurl.Qualifiers{{Key: purlVCSURLQualifier, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}} } return nil } diff --git a/syft/pkg/rpmdb_metadata.go b/syft/pkg/rpmdb_metadata.go index e1f6a1786f8..10e20433412 100644 --- a/syft/pkg/rpmdb_metadata.go +++ b/syft/pkg/rpmdb_metadata.go @@ -63,6 +63,10 @@ func (m RpmdbMetadata) PackageURL(distro *linux.Release) string { qualifiers[purlEpochQualifier] = strconv.Itoa(*m.Epoch) } + if m.SourceRpm != "" { + qualifiers[purlUpstreamQualifier] = m.SourceRpm + } + return packageurl.NewPackageURL( packageurl.TypeRPM, namespace, diff --git a/syft/pkg/rpmdb_metadata_test.go b/syft/pkg/rpmdb_metadata_test.go index 5c7b3bbb8b2..b33f329d84f 100644 --- a/syft/pkg/rpmdb_metadata_test.go +++ b/syft/pkg/rpmdb_metadata_test.go @@ -18,33 +18,33 @@ func TestRpmMetadata_pURL(t *testing.T) { expected string }{ { - name: "with arch and epoch", + name: "go case", distro: &linux.Release{ - ID: "centos", - VersionID: "7", + ID: "rhel", + VersionID: "8.4", }, metadata: RpmdbMetadata{ Name: "p", Version: "v", - Arch: "a", Release: "r", - Epoch: intRef(1), + Epoch: nil, }, - expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7", + expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4", }, { - name: "go case", + name: "with arch and epoch", distro: &linux.Release{ - ID: "rhel", - VersionID: "8.4", + ID: "centos", + VersionID: "7", }, metadata: RpmdbMetadata{ Name: "p", Version: "v", + Arch: "a", Release: "r", - Epoch: nil, + Epoch: intRef(1), }, - expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4", + expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7", }, { name: "missing distro", @@ -56,6 +56,20 @@ func TestRpmMetadata_pURL(t *testing.T) { }, expected: "pkg:rpm/p@v-r", }, + { + name: "with upstream source rpm info", + distro: &linux.Release{ + ID: "rhel", + VersionID: "8.4", + }, + metadata: RpmdbMetadata{ + Name: "p", + Version: "v", + Release: "r", + SourceRpm: "sourcerpm", + }, + expected: "pkg:rpm/rhel/p@v-r?upstream=sourcerpm&distro=rhel-8.4", + }, } for _, test := range tests { diff --git a/syft/pkg/url.go b/syft/pkg/url.go index 5187b6a0a3c..e62157d2b24 100644 --- a/syft/pkg/url.go +++ b/syft/pkg/url.go @@ -14,7 +14,10 @@ const ( purlArchQualifier = "arch" purlDistroQualifier = "distro" purlEpochQualifier = "epoch" - purlVCSURL = "vcs_url" + purlVCSURLQualifier = "vcs_url" + + // this qualifier is not in the pURL spec, but is used by grype to perform indirect matching based on source information + purlUpstreamQualifier = "upstream" ) type urlIdentifier interface {