Track supporting DPKG evidence (#3228)

* add dpkg evidence support

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use path over filepath

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2024-10-04 11:07:29 -04:00 committed by GitHub
parent 770fdc53ea
commit 13c6876906
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 29 deletions

View File

@ -9,15 +9,18 @@ func EvidentBy(catalog *pkg.Collection) []artifact.Relationship {
var edges []artifact.Relationship var edges []artifact.Relationship
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
for _, l := range p.Locations.ToSlice() { for _, l := range p.Locations.ToSlice() {
if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists || v != pkg.PrimaryEvidenceAnnotation { kind := pkg.SupportingEvidenceAnnotation
// skip non-primary evidence from being expressed as a relationship. if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; exists {
// note: this may be configurable in the future. kind = v
continue
} }
edges = append(edges, artifact.Relationship{ edges = append(edges, artifact.Relationship{
From: p, From: p,
To: l.Coordinates, To: l.Coordinates,
Type: artifact.EvidentByRelationship, Type: artifact.EvidentByRelationship,
Data: map[string]string{
"kind": kind,
},
}) })
} }
} }

View File

@ -134,9 +134,10 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
} }
if selection == file.FilesOwnedByPackageSelection { if selection == file.FilesOwnedByPackageSelection {
var coordinates []file.Coordinates var coordinates file.CoordinateSet
accessor.ReadFromSBOM(func(sbom *sbom.SBOM) { accessor.ReadFromSBOM(func(sbom *sbom.SBOM) {
// get any file coordinates that are owned by a package
for _, r := range sbom.Relationships { for _, r := range sbom.Relationships {
if r.Type != artifact.ContainsRelationship { if r.Type != artifact.ContainsRelationship {
continue continue
@ -145,16 +146,23 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
continue continue
} }
if c, ok := r.To.(file.Coordinates); ok { if c, ok := r.To.(file.Coordinates); ok {
coordinates = append(coordinates, c) coordinates.Add(c)
} }
} }
// get any file coordinates referenced by a package directly
for p := range sbom.Artifacts.Packages.Enumerate() {
coordinates.Add(p.Locations.CoordinateSet().ToSlice()...)
}
}) })
if len(coordinates) == 0 { coords := coordinates.ToSlice()
if len(coords) == 0 {
return nil, false return nil, false
} }
return coordinates, true return coords, true
} }
return nil, false return nil, false

View File

@ -25,15 +25,16 @@ func TestDpkgCataloger(t *testing.T) {
Version: "1.1.8-3.6", Version: "1.1.8-3.6",
FoundBy: "dpkg-db-cataloger", FoundBy: "dpkg-db-cataloger",
Licenses: pkg.NewLicenseSet( Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")), pkg.NewLicenseFromLocations("GPL-1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")), pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocations("LGPL-2.1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")), pkg.NewLicenseFromLocations("LGPL-2.1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
), ),
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"), file.NewLocation("/var/lib/dpkg/status"),
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"), file.NewLocation("/var/lib/dpkg/info/libpam-runtime.preinst"),
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"), file.NewLocation("/var/lib/dpkg/info/libpam-runtime.md5sums"),
file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"), file.NewLocation("/var/lib/dpkg/info/libpam-runtime.conffiles"),
file.NewLocation("/usr/share/doc/libpam-runtime/copyright"),
), ),
Type: pkg.DebPkg, Type: pkg.DebPkg,
Metadata: pkg.DpkgDBEntry{ Metadata: pkg.DpkgDBEntry{
@ -98,14 +99,15 @@ func TestDpkgCataloger(t *testing.T) {
Version: "3.34.1-3", Version: "3.34.1-3",
FoundBy: "dpkg-db-cataloger", FoundBy: "dpkg-db-cataloger",
Licenses: pkg.NewLicenseSet( Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("public-domain", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")), pkg.NewLicenseFromLocations("public-domain", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocations("GPL-2+", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")), pkg.NewLicenseFromLocations("GPL-2+", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")), pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
), ),
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0", "/var/lib/dpkg/status.d/libsqlite3-0"), file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0"),
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0.md5sums", "/var/lib/dpkg/status.d/libsqlite3-0.md5sums"), file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0.md5sums"),
file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright"), file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0.preinst"),
file.NewLocation("/usr/share/doc/libsqlite3-0/copyright"),
), ),
Type: pkg.DebPkg, Type: pkg.DebPkg,
Metadata: pkg.DpkgDBEntry{ Metadata: pkg.DpkgDBEntry{

View File

@ -22,14 +22,18 @@ const (
docsPath = "/usr/share/doc" docsPath = "/usr/share/doc"
) )
func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release) pkg.Package { func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release, evidence ...file.Location) pkg.Package {
// TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function // TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
licenses := make([]pkg.License, 0) licenses := make([]pkg.License, 0)
locations := file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
locations.Add(evidence...)
p := pkg.Package{ p := pkg.Package{
Name: d.Package, Name: d.Package,
Version: d.Version, Version: d.Version,
Licenses: pkg.NewLicenseSet(licenses...), Licenses: pkg.NewLicenseSet(licenses...),
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Locations: locations,
PURL: packageURL(d, release), PURL: packageURL(d, release),
Type: pkg.DebPkg, Type: pkg.DebPkg,
Metadata: d, Metadata: d,
@ -88,7 +92,7 @@ func packageURL(m pkg.DpkgDBEntry, distro *linux.Release) string {
func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
metadata, ok := p.Metadata.(pkg.DpkgDBEntry) metadata, ok := p.Metadata.(pkg.DpkgDBEntry)
if !ok { if !ok {
log.WithFields("package", p).Warn("unable to extract DPKG metadata to add licenses") log.WithFields("package", p).Trace("unable to extract DPKG metadata to add licenses")
return return
} }
@ -110,7 +114,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
func mergeFileListing(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { func mergeFileListing(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
metadata, ok := p.Metadata.(pkg.DpkgDBEntry) metadata, ok := p.Metadata.(pkg.DpkgDBEntry)
if !ok { if !ok {
log.WithFields("package", p).Warn("unable to extract DPKG metadata to file listing") log.WithFields("package", p).Trace("unable to extract DPKG metadata to file listing")
return return
} }
@ -204,7 +208,7 @@ func fetchMd5Contents(resolver file.Resolver, dbLocation file.Location, m pkg.Dp
// this is unexpected, but not a show-stopper // this is unexpected, but not a show-stopper
md5Reader, err = resolver.FileContentsByLocation(*location) md5Reader, err = resolver.FileContentsByLocation(*location)
if err != nil { if err != nil {
log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err) log.Tracef("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err)
} }
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation) l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
@ -239,7 +243,7 @@ func fetchConffileContents(resolver file.Resolver, dbLocation file.Location, m p
// this is unexpected, but not a show-stopper // this is unexpected, but not a show-stopper
reader, err = resolver.FileContentsByLocation(*location) reader, err = resolver.FileContentsByLocation(*location)
if err != nil { if err != nil {
log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err) log.Tracef("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err)
} }
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation) l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
@ -263,7 +267,7 @@ func fetchCopyrightContents(resolver file.Resolver, dbLocation file.Location, m
reader, err := resolver.FileContentsByLocation(*location) reader, err := resolver.FileContentsByLocation(*location)
if err != nil { if err != nil {
log.Warnf("failed to fetch deb copyright contents (package=%s): %s", m.Package, err) log.Tracef("failed to fetch deb copyright contents (package=%s): %s", m.Package, err)
} }
defer internal.CloseAndLogError(reader, location.RealPath) defer internal.CloseAndLogError(reader, location.RealPath)

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"path"
"regexp" "regexp"
"strings" "strings"
@ -34,12 +35,41 @@ func parseDpkgDB(_ context.Context, resolver file.Resolver, env *generic.Environ
var pkgs []pkg.Package var pkgs []pkg.Package
for _, m := range metadata { for _, m := range metadata {
pkgs = append(pkgs, newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease)) p := newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease, findDpkgInfoFiles(m.Package, resolver, reader.Location)...)
pkgs = append(pkgs, p)
} }
return pkgs, nil, nil return pkgs, nil, nil
} }
func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location {
if resolver == nil {
return nil
}
if strings.TrimSpace(name) == "" {
return nil
}
// for typical debian-base distributions, the installed package info is at /var/lib/dpkg/status
// and the md5sum information is under /var/lib/dpkg/info/; however, for distroless the installed
// package info is across multiple files under /var/lib/dpkg/status.d/ and the md5sums are contained in
// the same directory
searchPath := path.Dir(dbLocation.RealPath)
if !strings.HasSuffix(searchPath, "status.d") {
searchPath = path.Join(searchPath, "info")
}
// look for /var/lib/dpkg/info/NAME.*
locations, err := resolver.FilesByGlob(path.Join(searchPath, name+".*"))
if err != nil {
log.WithFields("error", err, "pkg", name).Trace("failed to fetch related dpkg info files")
return nil
}
return locations
}
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed. // parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
func parseDpkgStatus(reader io.Reader) ([]pkg.DpkgDBEntry, error) { func parseDpkgStatus(reader io.Reader) ([]pkg.DpkgDBEntry, error) {
buffedReader := bufio.NewReader(reader) buffedReader := bufio.NewReader(reader)

View File

@ -0,0 +1 @@
# some shell script...