Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion datastore/postgres/packagesbylayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ SELECT
source_package.arch,
package_scanartifact.package_db,
package_scanartifact.repository_hint,
package_scanartifact.filepath
package_scanartifact.filepath,
scanner.name,
scanner.version,
scanner.kind
FROM
package_scanartifact
LEFT JOIN package ON
Expand All @@ -74,6 +77,7 @@ FROM
package_scanartifact.source_id
= source_package.id
JOIN layer ON layer.hash = $1
JOIN package_scanartifact.scanner_id = scanner.id
WHERE
package_scanartifact.layer_id = layer.id
AND package_scanartifact.scanner_id = ANY ($2);
Expand Down
47 changes: 47 additions & 0 deletions gobin/purl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gobin

import (
"context"

"github.com/package-url/packageurl-go"
"github.com/quay/claircore"
"github.com/quay/claircore/pkg/purl"
)

const (
// purlType is the type of package URL for go binaries.
purlType = "golang"
)

func init() {
var d *Detector
purl.RegisterScanner(d, GeneratePURL)

Check failure on line 18 in gobin/purl.go

View workflow job for this annotation

GitHub Actions / Tests (stable)

cannot use GeneratePURL (value of type func(ctx context.Context, ir *claircore.IndexRecord) packageurl.PackageURL) as purl.GenerateFunc value in argument to purl.RegisterScanner

Check failure on line 18 in gobin/purl.go

View workflow job for this annotation

GitHub Actions / Tests (oldstable)

cannot use GeneratePURL (value of type func(ctx context.Context, ir *claircore.IndexRecord) packageurl.PackageURL) as purl.GenerateFunc value in argument to purl.RegisterScanner
purl.RegisterParse(purlType, ParsePURL)
}

func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) packageurl.PackageURL {
return packageurl.PackageURL{
Type: purlType,
Name: ir.Package.Name,
Version: ir.Package.Version,
Qualifiers: packageurl.QualifiersFromMap(map[string]string{
"arch": ir.Package.Arch,
}),
}
}

func ParsePURL(ctx context.Context, purl packageurl.PackageURL) (*claircore.IndexRecord, error) {
pVersion, err := ParseVersion(purl.Version)
if err != nil {
return nil, err
}
return &claircore.IndexRecord{
Package: &claircore.Package{
Name: purl.Name,
Version: purl.Version,
NormalizedVersion: pVersion,
Arch: purl.Qualifiers.Map()["arch"],
},
Repository: &Repository,
}, nil
}
6 changes: 6 additions & 0 deletions package.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ type Package struct {
Arch string `json:"arch,omitempty"`
// CPE name for package
CPE cpe.WFN `json:"cpe,omitempty"`
// Scanner name
ScannerName string `json:"scanner_name,omitempty"`
// Scanner version
ScannerVersion string `json:"scanner_version,omitempty"`
// Scanner kind
ScannerKind string `json:"scanner_kind,omitempty"`
}

const (
Expand Down
62 changes: 62 additions & 0 deletions pkg/purl/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package purl

import (
"context"
"fmt"
"sync"

"github.com/package-url/packageurl-go"
"github.com/quay/claircore"
"github.com/quay/claircore/indexer"
)

// GenerateFunc produces a PackageURL for a given IndexRecord.
// Implementations should be deterministic and side-effect free.
type GenerateFunc func(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error)

// ParseFunc produces an IndexRecord for a given PackageURL.
// Implementations should be deterministic and side-effect free.
type ParseFunc func(ctx context.Context, purl packageurl.PackageURL) (*claircore.IndexRecord, error)

var (
mu sync.RWMutex
genRegistry = map[string]GenerateFunc{}
parseRegistry = map[string]ParseFunc{}
)

// RegisterScanner registers using an indexer.PackageScanner (for its Name()).
func RegisterScanner(s indexer.PackageScanner, GenFn GenerateFunc) {
mu.Lock()
genRegistry[s.Name()] = GenFn
mu.Unlock()
}

func RegisterParse(purlType string, ParseFn ParseFunc) {
mu.Lock()
parseRegistry[purlType] = ParseFn
mu.Unlock()
}

// Generate finds a registered generator by the record's scanner name and
// returns the generated PackageURL.
func Generate(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) {
mu.RLock()
f, ok := genRegistry[ir.Package.ScannerName]
mu.RUnlock()
if !ok {
return packageurl.PackageURL{}, fmt.Errorf("no PURL generator registered for scanner %q", ir.Package.ScannerName)
}
return f(ctx, ir)
}

// Parse finds a registered generator by the record's scanner name and
// returns the generated PackageURL.
func Parse(ctx context.Context, purl packageurl.PackageURL) (*claircore.IndexRecord, error) {
mu.RLock()
f, ok := parseRegistry[purl.Type]
mu.RUnlock()
if !ok {
return &claircore.IndexRecord{}, fmt.Errorf("no PURL generator registered for purl type %q", purl.Type)
}
return f(ctx, purl)
}
41 changes: 41 additions & 0 deletions rhel/purl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package rhel

import (
"context"
"net/url"

"github.com/package-url/packageurl-go"
"github.com/quay/claircore"
"github.com/quay/claircore/pkg/purl"
)

const (
// purlType is the type of package URL for go binaries.
purlType = "rpm"
purlNamespace = "redhat"
)

func init() {
var s *PackageScanner
purl.RegisterScanner(s, GenerateRPMPURL)
}

func GenerateRPMPURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) {
qs := map[string]string{
"arch": ir.Package.Arch,
}
if ir.Repository != nil {
// Encode to keep the qualifier syntactically safe
qs["repository_cpe"] = url.QueryEscape(ir.Repository.CPE.String())
}
if ir.Package.Module != "" {
qs["rpmmod"] = ir.Package.Module
}
return packageurl.PackageURL{
Type: purlType,
Namespace: purlNamespace,
Name: ir.Package.Name,
Version: ir.Package.Version,
Qualifiers: packageurl.QualifiersFromMap(qs),
}, nil
}
13 changes: 13 additions & 0 deletions sbom/spdx/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"github.com/spdx/tools-golang/spdx/v2/v2_3"

"github.com/quay/claircore"
"github.com/quay/claircore/pkg/purl"
"github.com/quay/claircore/sbom"
"github.com/quay/zlog"
)

// Version describes the SPDX version to target.
Expand Down Expand Up @@ -195,6 +197,17 @@ func (e *Encoder) parseIndexReport(ctx context.Context, ir *claircore.IndexRepor
pkg.PackageFileName = pkgDB
pkg.FilesAnalyzed = true
pkg.PrimaryPackagePurpose = pkgPurpose
purl, err := purl.Generate(ctx, r)
switch {
case err != nil:
zlog.Error(ctx).Err(err).Msg("failed to generate PURL")
default:
pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, &v2_3.PackageExternalReference{
Category: "OTHER",
RefType: "purl",
Locator: purl.String(),
})
}

pkgs[pkgID] = pkg
pkgIDs = append(pkgIDs, pkgID)
Expand Down
3 changes: 3 additions & 0 deletions sbom/spdx/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

_ "github.com/quay/claircore/gobin"
_ "github.com/quay/claircore/rhel"

"github.com/quay/claircore"
)

Expand Down
11 changes: 9 additions & 2 deletions sbom/spdx/testdata/simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@
"cpe" : ""
},
"normalized_version" : "",
"cpe" : ""
"cpe" : "",
"scanner_name" : "rhel-package-scanner",
"scanner_version" : "1",
"scanner_kind" : "package"
},
"456" : {
"id" : "456",
"name" : "package B",
"version" : "v2.0.0",
"kind" : "binary",
"normalized_version" : "",
"cpe" : ""
"cpe" : "",
"scanner_name" : "java",
"scanner_version" : "8",
"scanner_kind" : "package"

}
},
"distributions" : {
Expand Down
Loading