Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐️ Add Purl to macOS and Windows systems #4996

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions providers/os/resources/packages/aix_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"io"
"strings"

"github.com/package-url/packageurl-go"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
cpe2 "go.mondoo.com/cnquery/v11/providers/os/resources/cpe"
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"
Expand Down Expand Up @@ -43,8 +42,10 @@ func parseAixPackages(pf *inventory.Platform, r io.Reader) ([]Package, error) {
Version: record[2],
Description: strings.TrimSpace(record[6]),
Format: AixPkgFormat,
PUrl: purl.NewPackageUrl(pf, record[1], record[2], "", "", packageurl.TypeGeneric),
CPEs: cpes,
PUrl: purl.NewPackageURL(
pf, purl.TypeGeneric, record[1], record[2], purl.WithNamespace(pf.Name),
).String(),
CPEs: cpes,
})

}
Expand Down
2 changes: 1 addition & 1 deletion providers/os/resources/packages/aix_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestParseAixPackages(t *testing.T) {
Name: "X11.apps.msmit",
Version: "7.3.0.0",
Description: "AIXwindows msmit Application",
PUrl: "pkg:generic/aix/X11.apps.msmit@7.3.0.0?distro=aix-7.2",
PUrl: "pkg:generic/aix/X11.apps.msmit@7.3.0.0?arch=powerpc&distro=aix-7.2",
CPEs: []string{
"cpe:2.3:a:x11.apps.msmit:x11.apps.msmit:7.3.0.0:*:*:*:*:*:powerpc:*",
"cpe:2.3:a:x11.apps.msmit:x11.apps.msmit:7.3.0:*:*:*:*:*:powerpc:*",
Expand Down
6 changes: 4 additions & 2 deletions providers/os/resources/packages/apk_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"path/filepath"
"regexp"

"github.com/package-url/packageurl-go"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
cpe2 "go.mondoo.com/cnquery/v11/providers/os/resources/cpe"
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"
Expand Down Expand Up @@ -44,7 +43,10 @@ func ParseApkDbPackages(pf *inventory.Platform, input io.Reader) []Package {
}

pkg.Format = AlpinePkgFormat
pkg.PUrl = purl.NewPackageUrl(pf, pkg.Name, pkg.Version, pkg.Arch, pkg.Epoch, packageurl.TypeApk)
pkg.PUrl = purl.NewPackageURL(pf, purl.TypeApk, pkg.Name, pkg.Version,
purl.WithArch(pkg.Arch),
purl.WithEpoch(pkg.Epoch),
).String()

cpes, _ := cpe2.NewPackage2Cpe(pkg.Vendor, pkg.Name, pkg.Version, "", pf.Arch)
pkg.CPEs = cpes
Expand Down
6 changes: 4 additions & 2 deletions providers/os/resources/packages/dpkg_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"regexp"
"strings"

"github.com/package-url/packageurl-go"
"github.com/rs/zerolog/log"
"github.com/spf13/afero"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
Expand Down Expand Up @@ -39,7 +38,10 @@ func ParseDpkgPackages(pf *inventory.Platform, input io.Reader) ([]Package, erro
add := func(pkg Package) {
// do sanitization checks to ensure we have minimal information
if pkg.Name != "" && pkg.Version != "" {
pkg.PUrl = purl.NewPackageUrl(pf, pkg.Name, pkg.Version, pkg.Arch, pkg.Epoch, packageurl.TypeDebian)
pkg.PUrl = purl.NewPackageURL(pf, purl.TypeDebian, pkg.Name, pkg.Version,
purl.WithArch(pkg.Arch),
purl.WithEpoch(pkg.Epoch),
).String()
cpes, _ := cpe.NewPackage2Cpe(pkg.Name, pkg.Name, pkg.Version, pkg.Epoch, pkg.Arch)
cpesWithoutArch, _ := cpe.NewPackage2Cpe(pkg.Name, pkg.Name, pkg.Version, pkg.Epoch, "")
cpes = append(cpes, cpesWithoutArch...)
Expand Down
6 changes: 3 additions & 3 deletions providers/os/resources/packages/macos_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ func ParseMacOSPackages(platform *inventory.Platform, input io.Reader) ([]Packag
pkgs[i].Version = entry.Version
pkgs[i].Format = MacosPkgFormat
pkgs[i].FilesAvailable = PkgFilesIncluded
pkgs[i].PUrl = purl.NewPackageUrl(
platform, entry.Name, entry.Version, platform.Arch, "", purl.TypeMacos,
)
pkgs[i].PUrl = purl.NewPackageURL(
platform, purl.TypeMacos, entry.Name, entry.Version,
).String()
if entry.Path != "" {
pkgs[i].Files = []FileRecord{
{
Expand Down
4 changes: 2 additions & 2 deletions providers/os/resources/packages/macos_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ func TestMacOsXPackageParser(t *testing.T) {
assert.Equal(t, "10.0", m[0].Version, "pkg version detected")
assert.Equal(t, packages.MacosPkgFormat, m[0].Format, "pkg format detected")
assert.Equal(t, packages.PkgFilesIncluded, m[0].FilesAvailable)
assert.Equal(t, "pkg:macos/Preview@10.0?arch=x86_64&distro=macos-15.2", m[0].PUrl)
assert.Equal(t, "pkg:macos/Preview@10.0?arch=x86_64", m[0].PUrl)
assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Preview.app"}}, m[0].Files)

assert.Equal(t, "Contacts", m[1].Name, "pkg name detected")
assert.Equal(t, "11.0", m[1].Version, "pkg version detected")
assert.Equal(t, packages.MacosPkgFormat, m[1].Format, "pkg format detected")
assert.Equal(t, packages.PkgFilesIncluded, m[1].FilesAvailable)
assert.Equal(t, "pkg:macos/Contacts@11.0?arch=x86_64&distro=macos-15.2", m[1].PUrl)
assert.Equal(t, "pkg:macos/Contacts@11.0?arch=x86_64", m[1].PUrl)
assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Contacts.app"}}, m[1].Files)
}
8 changes: 4 additions & 4 deletions providers/os/resources/packages/pacman_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ package packages
import (
"bufio"
"fmt"
"github.com/package-url/packageurl-go"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"
"io"
"regexp"

"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"

"github.com/cockroachdb/errors"
"go.mondoo.com/cnquery/v11/providers/os/connection/shared"
)
Expand All @@ -35,7 +35,7 @@ func ParsePacmanPackages(pf *inventory.Platform, input io.Reader) []Package {
Name: name,
Version: version,
Format: PacmanPkgFormat,
PUrl: purl.NewPackageUrl(pf, name, version, "", "", packageurl.TypeAlpm),
PUrl: purl.NewPackageURL(pf, purl.TypeAlpm, name, version).String(),
})
}
}
Expand Down
11 changes: 6 additions & 5 deletions providers/os/resources/packages/pacman_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
package packages_test

import (
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
"strings"
"testing"

"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"

"github.com/stretchr/testify/assert"
"go.mondoo.com/cnquery/v11/providers/os/resources/packages"
)
Expand Down Expand Up @@ -38,23 +39,23 @@ zziplib 0.13.67-1`
p := packages.Package{
Name: "qpdfview",
Version: "0.4.17beta1-4.1",
PUrl: "pkg:alpm/arch/qpdfview@0.4.17beta1-4.1?distro=arch",
PUrl: "pkg:alpm/arch/qpdfview@0.4.17beta1-4.1?arch=x86_64&distro=arch",
Format: packages.PacmanPkgFormat,
}
assert.Contains(t, m, p, "pkg detected")

p = packages.Package{
Name: "vertex-maia-themes",
Version: "20171114-1",
PUrl: "pkg:alpm/arch/vertex-maia-themes@20171114-1?distro=arch",
PUrl: "pkg:alpm/arch/vertex-maia-themes@20171114-1?arch=x86_64&distro=arch",
Format: packages.PacmanPkgFormat,
}
assert.Contains(t, m, p, "pkg detected")

p = packages.Package{
Name: "xfce4-pulseaudio-plugin",
Version: "0.3.2.r13.g553691a-1",
PUrl: "pkg:alpm/arch/xfce4-pulseaudio-plugin@0.3.2.r13.g553691a-1?distro=arch",
PUrl: "pkg:alpm/arch/xfce4-pulseaudio-plugin@0.3.2.r13.g553691a-1?arch=x86_64&distro=arch",
Format: packages.PacmanPkgFormat,
}
assert.Contains(t, m, p, "pkg detected")
Expand Down Expand Up @@ -84,7 +85,7 @@ argon2 20190702-2`
p := packages.Package{
Name: "acl",
Version: "2.2.53-2",
PUrl: "pkg:alpm/arch/acl@2.2.53-2?distro=arch",
PUrl: "pkg:alpm/arch/acl@2.2.53-2?arch=x86_64&distro=arch",
Format: packages.PacmanPkgFormat,
}
assert.Contains(t, m, p, "pkg detected")
Expand Down
6 changes: 4 additions & 2 deletions providers/os/resources/packages/rpm_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"strconv"
"strings"

"github.com/package-url/packageurl-go"
"go.mondoo.com/cnquery/v11/providers/os/resources/cpe"
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"

Expand Down Expand Up @@ -94,9 +93,12 @@ func newRpmPackage(pf *inventory.Platform, name, version, arch, epoch, vendor, d
Arch: arch,
Description: description,
Format: RpmPkgFormat,
PUrl: purl.NewPackageUrl(pf, name, version, arch, epoch, packageurl.TypeRPM),
CPEs: cpes,
Vendor: vendor,
PUrl: purl.NewPackageURL(pf, purl.TypeRPM, name, version,
purl.WithArch(arch),
purl.WithEpoch(epoch),
).String(),
}
}

Expand Down
10 changes: 4 additions & 6 deletions providers/os/resources/packages/windows_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ func (p winAppxPackages) toPackage(platform *inventory.Platform) Package {
Arch: p.arch,
Format: "windows/appx",
Vendor: p.Publisher,
PUrl: purl.NewPackageUrl(
platform, p.Name, p.Version, platform.Arch, "", purl.TypeWindowsAppx,
),
PUrl: purl.NewPackageURL(platform, purl.TypeAppx, p.Name, p.Version).String(),
}

if p.Name != "" && p.Version != "" {
Expand Down Expand Up @@ -560,9 +558,9 @@ func ParseWindowsAppPackages(platform *inventory.Platform, input io.Reader) ([]P
Format: "windows/app",
CPEs: cpeWfns,
Vendor: entry.Publisher,
PUrl: purl.NewPackageUrl(
platform, entry.DisplayName, entry.DisplayVersion, platform.Arch, "", purl.TypeWindows,
),
PUrl: purl.NewPackageURL(
platform, purl.TypeWindows, entry.DisplayName, entry.DisplayVersion,
).String(),
})
}

Expand Down
6 changes: 3 additions & 3 deletions providers/os/resources/packages/windows_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestWindowsAppPackagesParser(t *testing.T) {
Version: "14.28.29913.0",
Arch: "",
Format: "windows/app",
PUrl: `pkg:windows/Microsoft%20Visual%20C%2B%2B%202015-2019%20Redistributable%20%28x86%29%20-%2014.28.29913@14.28.29913.0?arch=x86&distro=windows-10.0.18363`,
PUrl: `pkg:windows/Microsoft%20Visual%20C%2B%2B%202015-2019%20Redistributable%20%28x86%29%20-%2014.28.29913@14.28.29913.0?arch=x86`,
CPEs: []string{
"cpe:2.3:a:microsoft_corporation:microsoft_visual_c\\+\\+_2015-2019_redistributable_\\(x86\\)_-_14.28.29913:14.28.29913.0:*:*:*:*:*:*:*",
"cpe:2.3:a:microsoft:microsoft_visual_c\\+\\+_2015-2019_redistributable_\\(x86\\)_-_14.28.29913:14.28.29913.0:*:*:*:*:*:*:*",
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestWindowsAppxPackagesParser(t *testing.T) {
Version: "1.11.5.17763",
Arch: "neutral",
Format: "windows/appx",
PUrl: "pkg:appx/windows/Microsoft.Windows.Cortana@1.11.5.17763?arch=x86&distro=windows-10.0.18363",
PUrl: "pkg:appx/Microsoft.Windows.Cortana@1.11.5.17763?arch=x86",
// TODO: this is a bug in the CPE generation, we need to extract the publisher from the package
CPEs: []string{
"cpe:2.3:a:cn\\=microsoft_corporation\\,_o\\=microsoft_corporation\\,_l\\=redmond\\,_s\\=washington\\,_c\\=us:microsoft.windows.cortana:1.11.5.17763:*:*:*:*:*:*:*",
Expand Down Expand Up @@ -232,7 +232,7 @@ func TestToPackage(t *testing.T) {
Version: "1.11.5.17763",
Arch: "x86",
Format: "windows/appx",
PUrl: "pkg:appx/windows/Microsoft.Windows.Cortana@1.11.5.17763?arch=x86&distro=windows-10.0.18363",
PUrl: "pkg:appx/Microsoft.Windows.Cortana@1.11.5.17763?arch=x86",
Vendor: "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
CPEs: []string{
"cpe:2.3:a:cn\\=microsoft_corporation\\,_o\\=microsoft_corporation\\,_l\\=redmond\\,_s\\=washington\\,_c\\=us:microsoft.windows.cortana:1.11.5.17763:*:*:*:*:*:*:*",
Expand Down
117 changes: 91 additions & 26 deletions providers/os/resources/purl/purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ const (
QualifierEpoch = "epoch"
)

// PackageURL is a helper struct that renters a package url based of an inventory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renders instead of renters

// platform, purl type, and modifiers.
type PackageURL struct {
// Required: minimal attributes to render a PURL.
Type Type
Name string
Version string

// Optional: can be set via modifiers.
Namespace string
Arch string
Epoch string

// Used as metadata to fetch things like the architecture or linux distribution.
platform *inventory.Platform
}

// NewQualifiers creates a new Qualifiers slice from a map of key/value pairs.
// see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst for more information
func NewQualifiers(qualifier map[string]string) packageurl.Qualifiers {
Expand All @@ -43,44 +60,92 @@ func NewQualifiers(qualifier map[string]string) packageurl.Qualifiers {
return list
}

// NewPackageUrl creates a new package url for a given platform, name, version, arch, epoch and purlType
// see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst for more information
func NewPackageUrl(pf *inventory.Platform, name string, version string, arch string, epoch string, purlType string) string {
qualifiers := map[string]string{}
if arch != "" {
qualifiers[QualifierArch] = arch
// NewPackageURL creates a new package url for a given platform, name, version, and type.
//
// For more information, see:
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
func NewPackageURL(pf *inventory.Platform, t Type, name, version string, modifiers ...Modifier) *PackageURL {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That made me stumble.
First thought was, that we could derive the type from the platform. But no:

  • Windows: windows, appx, nuget, ...
  • Ubuntu: deb, snap, brew, ...
  • ...

🫤

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we really can't 😭

purl := &PackageURL{
Type: t,
Name: name,
Version: version,
platform: pf,
}

if epoch != "" && epoch != "0" {
qualifiers[QualifierEpoch] = epoch
// if a platform was provided
if pf != nil {
// use the platform architecture for the package
purl.Arch = pf.Arch

// and if the distro is set via labels, set it as a namespace
if pf.Labels != nil && pf.Labels[detector.LabelDistroID] != "" {
purl.Namespace = pf.Labels[detector.LabelDistroID]
}
}

namespace := pf.Name
if pf.Labels != nil && pf.Labels[detector.LabelDistroID] != "" {
namespace = pf.Labels[detector.LabelDistroID]
// apply modifiers
for _, modifier := range modifiers {
modifier(purl)
}

// generate distro qualifier
distroQualifiers := []string{}
distroQualifiers = append(distroQualifiers, namespace)
if pf.Version != "" {
distroQualifiers = append(distroQualifiers, pf.Version)
} else if pf.Build != "" {
distroQualifiers = append(distroQualifiers, pf.Build)
return purl
}

func (purl PackageURL) String() string {
qualifiers := map[string]string{}
if purl.Arch != "" {
qualifiers[QualifierArch] = purl.Arch
}
qualifiers[QualifierDistro] = strings.Join(distroQualifiers, "-")

// Avoids creating purl's like: 'pkg:macos/macos/Package'
if namespace == purlType {
namespace = ""
if purl.Epoch != "" && purl.Epoch != "0" {
qualifiers[QualifierEpoch] = purl.Epoch
}

if distroQualifiers, ok := purl.distroQualifiers(); ok {
qualifiers[QualifierDistro] = distroQualifiers
}

return packageurl.NewPackageURL(
purlType,
namespace,
name,
version,
string(purl.Type),
purl.Namespace,
purl.Name,
purl.Version,
NewQualifiers(qualifiers),
"",
).ToString()
}

// generate distro qualifier
func (purl PackageURL) distroQualifiers() (string, bool) {
if purl.Namespace == "" {
return "", false
}

distroQualifiers := []string{}
distroQualifiers = append(distroQualifiers, purl.Namespace)
if purl.platform.Version != "" {
distroQualifiers = append(distroQualifiers, purl.platform.Version)
} else if purl.platform.Build != "" {
distroQualifiers = append(distroQualifiers, purl.platform.Build)
}

return strings.Join(distroQualifiers, "-"), true
}

type Modifier func(*PackageURL)

func WithArch(arch string) Modifier {
return func(purl *PackageURL) {
purl.Arch = arch
}
}
func WithEpoch(epoch string) Modifier {
return func(purl *PackageURL) {
purl.Epoch = epoch
}
}
func WithNamespace(namespace string) Modifier {
return func(purl *PackageURL) {
purl.Namespace = namespace
}
}
Loading
Loading